From 3de60d1ddc0ca812d593302c6bd1ef94ff2bbc65 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 7 Oct 2024 14:36:06 +0100 Subject: [PATCH 01/35] PicoGraphics: Layers. Add preliminary support for multiple layered drawing surfaces. Allows, for example, static content to be loaded into one layer and remain unmodified while the above layer contains animations. Particularly useful for drawing PNG or JPEG UI elements which are then overdrawn with text or animated elements, without paying the cost of loading/decoding every frame. --- drivers/st7735/st7735.cpp | 22 +++--- drivers/st7789/st7789.cpp | 38 +++++----- libraries/pico_graphics/pico_graphics.cpp | 7 ++ libraries/pico_graphics/pico_graphics.hpp | 32 +++++--- .../pico_graphics/pico_graphics_pen_1bit.cpp | 6 +- .../pico_graphics/pico_graphics_pen_1bitY.cpp | 4 +- .../pico_graphics/pico_graphics_pen_3bit.cpp | 4 +- .../pico_graphics/pico_graphics_pen_inky7.cpp | 4 +- .../pico_graphics/pico_graphics_pen_p4.cpp | 4 +- .../pico_graphics/pico_graphics_pen_p8.cpp | 4 +- .../pico_graphics_pen_rgb332.cpp | 28 +++++-- .../pico_graphics_pen_rgb565.cpp | 74 +++++++++++++------ .../pico_graphics_pen_rgb888.cpp | 4 +- .../modules/picographics/picographics.c | 5 ++ .../modules/picographics/picographics.cpp | 63 ++++++++++------ .../modules/picographics/picographics.h | 3 + 16 files changed, 191 insertions(+), 111 deletions(-) diff --git a/drivers/st7735/st7735.cpp b/drivers/st7735/st7735.cpp index d2cabff7c..1ae5728eb 100644 --- a/drivers/st7735/st7735.cpp +++ b/drivers/st7735/st7735.cpp @@ -165,21 +165,17 @@ namespace pimoroni { // Native 16-bit framebuffer update void ST7735::update(PicoGraphics *graphics) { - if(graphics->pen_type == PicoGraphics::PEN_RGB565) { - command(reg::RAMWR, width * height * sizeof(uint16_t), (const char*)graphics->frame_buffer); - } else { - command(reg::RAMWR); - gpio_put(dc, 1); // data mode - gpio_put(cs, 0); + command(reg::RAMWR); + gpio_put(dc, 1); // data mode + gpio_put(cs, 0); - graphics->frame_convert(PicoGraphics::PEN_RGB565, [this](void *data, size_t length) { - if (length > 0) { - spi_write_blocking(spi, (const uint8_t*)data, length); - } - }); + graphics->frame_convert(PicoGraphics::PEN_RGB565, [this](void *data, size_t length) { + if (length > 0) { + spi_write_blocking(spi, (const uint8_t*)data, length); + } + }); - gpio_put(cs, 1); - } + gpio_put(cs, 1); } void ST7735::set_backlight(uint8_t brightness) { diff --git a/drivers/st7789/st7789.cpp b/drivers/st7789/st7789.cpp index ed34447c5..b76f9b078 100644 --- a/drivers/st7789/st7789.cpp +++ b/drivers/st7789/st7789.cpp @@ -282,30 +282,26 @@ namespace pimoroni { void ST7789::update(PicoGraphics *graphics) { uint8_t cmd = reg::RAMWR; - if(graphics->pen_type == PicoGraphics::PEN_RGB565) { // Display buffer is screen native - command(cmd, width * height * sizeof(uint16_t), (const char*)graphics->frame_buffer); - } else { - gpio_put(dc, 0); // command mode - gpio_put(cs, 0); - if(spi) { // SPI Bus - spi_write_blocking(spi, &cmd, 1); - } else { // Parallel Bus - write_blocking_parallel(&cmd, 1); - } + gpio_put(dc, 0); // command mode + gpio_put(cs, 0); + if(spi) { // SPI Bus + spi_write_blocking(spi, &cmd, 1); + } else { // Parallel Bus + write_blocking_parallel(&cmd, 1); + } - gpio_put(dc, 1); // data mode + gpio_put(dc, 1); // data mode - graphics->frame_convert(PicoGraphics::PEN_RGB565, [this](void *data, size_t length) { - if (length > 0) { - write_blocking_dma((const uint8_t*)data, length); - } - else { - dma_channel_wait_for_finish_blocking(st_dma); - } - }); + graphics->frame_convert(PicoGraphics::PEN_RGB565, [this](void *data, size_t length) { + if (length > 0) { + write_blocking_dma((const uint8_t*)data, length); + } + else { + dma_channel_wait_for_finish_blocking(st_dma); + } + }); - gpio_put(cs, 1); - } + gpio_put(cs, 1); } void ST7789::set_backlight(uint8_t brightness) { diff --git a/libraries/pico_graphics/pico_graphics.cpp b/libraries/pico_graphics/pico_graphics.cpp index 4e32e4ec3..58ae1ba70 100644 --- a/libraries/pico_graphics/pico_graphics.cpp +++ b/libraries/pico_graphics/pico_graphics.cpp @@ -19,6 +19,13 @@ namespace pimoroni { RGB* PicoGraphics::get_palette() {return nullptr;} bool PicoGraphics::supports_alpha_blend() {return false;} + void PicoGraphics::set_layer(uint l) { + this->layer = l; + }; + uint PicoGraphics::get_layer() { + return this->layer; + }; + void PicoGraphics::set_dimensions(int width, int height) { bounds = clip = {0, 0, width, height}; } diff --git a/libraries/pico_graphics/pico_graphics.hpp b/libraries/pico_graphics/pico_graphics.hpp index d6fadc083..66d74254c 100644 --- a/libraries/pico_graphics/pico_graphics.hpp +++ b/libraries/pico_graphics/pico_graphics.hpp @@ -226,6 +226,9 @@ namespace pimoroni { Rect clip; uint thickness = 1; + uint layers = 1; + uint layer = 0; + typedef std::function conversion_callback_func; typedef std::function next_pixel_func; typedef std::function next_pixel_func_rgb888; @@ -270,6 +273,12 @@ namespace pimoroni { PicoGraphics(uint16_t width, uint16_t height, void *frame_buffer) : frame_buffer(frame_buffer), bounds(0, 0, width, height), clip(0, 0, width, height) { set_font(&font6); + layers = 1; + }; + + PicoGraphics(uint16_t width, uint16_t height, uint16_t layers, void *frame_buffer) + : frame_buffer(frame_buffer), bounds(0, 0, width, height), clip(0, 0, width, height), layers(layers) { + set_font(&font6); }; virtual void set_pen(uint c) = 0; @@ -278,6 +287,9 @@ namespace pimoroni { virtual void set_pixel_span(const Point &p, uint l) = 0; void set_thickness(uint t); + void set_layer(uint l); + uint get_layer(); + virtual int get_palette_size(); virtual RGB* get_palette(); virtual bool supports_alpha_blend(); @@ -330,7 +342,7 @@ namespace pimoroni { public: uint8_t color; - PicoGraphics_Pen1Bit(uint16_t width, uint16_t height, void *frame_buffer); + PicoGraphics_Pen1Bit(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers = 1); void set_pen(uint c) override; void set_pen(uint8_t r, uint8_t g, uint8_t b) override; @@ -346,7 +358,7 @@ namespace pimoroni { public: uint8_t color; - PicoGraphics_Pen1BitY(uint16_t width, uint16_t height, void *frame_buffer); + PicoGraphics_Pen1BitY(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers = 1); void set_pen(uint c) override; void set_pen(uint8_t r, uint8_t g, uint8_t b) override; @@ -387,7 +399,7 @@ namespace pimoroni { bool cache_built = false; std::array candidates; - PicoGraphics_Pen3Bit(uint16_t width, uint16_t height, void *frame_buffer); + PicoGraphics_Pen3Bit(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers = 1); void set_pen(uint c) override; void set_pen(uint8_t r, uint8_t g, uint8_t b) override; @@ -420,7 +432,7 @@ namespace pimoroni { bool cache_built = false; std::array candidates; - PicoGraphics_PenP4(uint16_t width, uint16_t height, void *frame_buffer); + PicoGraphics_PenP4(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers = 1); void set_pen(uint c) override; void set_pen(uint8_t r, uint8_t g, uint8_t b) override; int update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) override; @@ -453,7 +465,7 @@ namespace pimoroni { bool cache_built = false; std::array candidates; - PicoGraphics_PenP8(uint16_t width, uint16_t height, void *frame_buffer); + PicoGraphics_PenP8(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers = 1); void set_pen(uint c) override; void set_pen(uint8_t r, uint8_t g, uint8_t b) override; int update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) override; @@ -478,7 +490,7 @@ namespace pimoroni { class PicoGraphics_PenRGB332 : public PicoGraphics { public: RGB332 color; - PicoGraphics_PenRGB332(uint16_t width, uint16_t height, void *frame_buffer); + PicoGraphics_PenRGB332(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers = 1); void set_pen(uint c) override; void set_pen(uint8_t r, uint8_t g, uint8_t b) override; int create_pen(uint8_t r, uint8_t g, uint8_t b) override; @@ -503,7 +515,7 @@ namespace pimoroni { public: RGB src_color; RGB565 color; - PicoGraphics_PenRGB565(uint16_t width, uint16_t height, void *frame_buffer); + PicoGraphics_PenRGB565(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers = 1); void set_pen(uint c) override; void set_pen(uint8_t r, uint8_t g, uint8_t b) override; int create_pen(uint8_t r, uint8_t g, uint8_t b) override; @@ -516,13 +528,15 @@ namespace pimoroni { static size_t buffer_size(uint w, uint h) { return w * h * sizeof(RGB565); } + + void frame_convert(PenType type, conversion_callback_func callback) override; }; class PicoGraphics_PenRGB888 : public PicoGraphics { public: RGB src_color; RGB888 color; - PicoGraphics_PenRGB888(uint16_t width, uint16_t height, void *frame_buffer); + PicoGraphics_PenRGB888(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers = 1); void set_pen(uint c) override; void set_pen(uint8_t r, uint8_t g, uint8_t b) override; int create_pen(uint8_t r, uint8_t g, uint8_t b) override; @@ -600,7 +614,7 @@ namespace pimoroni { uint color; IDirectDisplayDriver &driver; - PicoGraphics_PenInky7(uint16_t width, uint16_t height, IDirectDisplayDriver &direct_display_driver); + PicoGraphics_PenInky7(uint16_t width, uint16_t height, IDirectDisplayDriver &direct_display_driver, uint16_t layers = 1); void set_pen(uint c) override; void set_pen(uint8_t r, uint8_t g, uint8_t b) override; int create_pen(uint8_t r, uint8_t g, uint8_t b) override; diff --git a/libraries/pico_graphics/pico_graphics_pen_1bit.cpp b/libraries/pico_graphics/pico_graphics_pen_1bit.cpp index 30fb1e53b..e05b2a370 100644 --- a/libraries/pico_graphics/pico_graphics_pen_1bit.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_1bit.cpp @@ -2,11 +2,11 @@ namespace pimoroni { - PicoGraphics_Pen1Bit::PicoGraphics_Pen1Bit(uint16_t width, uint16_t height, void *frame_buffer) - : PicoGraphics(width, height, frame_buffer) { + PicoGraphics_Pen1Bit::PicoGraphics_Pen1Bit(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers) + : PicoGraphics(width, height, layers, frame_buffer) { this->pen_type = PEN_1BIT; if(this->frame_buffer == nullptr) { - this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); + this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height) * layers]); } } diff --git a/libraries/pico_graphics/pico_graphics_pen_1bitY.cpp b/libraries/pico_graphics/pico_graphics_pen_1bitY.cpp index 6fc2cb8c2..6efdde920 100644 --- a/libraries/pico_graphics/pico_graphics_pen_1bitY.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_1bitY.cpp @@ -2,8 +2,8 @@ namespace pimoroni { - PicoGraphics_Pen1BitY::PicoGraphics_Pen1BitY(uint16_t width, uint16_t height, void *frame_buffer) - : PicoGraphics(width, height, frame_buffer) { + PicoGraphics_Pen1BitY::PicoGraphics_Pen1BitY(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers) + : PicoGraphics(width, height, layers, frame_buffer) { this->pen_type = PEN_1BIT; if(this->frame_buffer == nullptr) { this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); diff --git a/libraries/pico_graphics/pico_graphics_pen_3bit.cpp b/libraries/pico_graphics/pico_graphics_pen_3bit.cpp index 5faba1cdc..dc67018b3 100644 --- a/libraries/pico_graphics/pico_graphics_pen_3bit.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_3bit.cpp @@ -2,8 +2,8 @@ namespace pimoroni { - PicoGraphics_Pen3Bit::PicoGraphics_Pen3Bit(uint16_t width, uint16_t height, void *frame_buffer) - : PicoGraphics(width, height, frame_buffer) { + PicoGraphics_Pen3Bit::PicoGraphics_Pen3Bit(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers) + : PicoGraphics(width, height, layers, frame_buffer) { this->pen_type = PEN_3BIT; if(this->frame_buffer == nullptr) { this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); diff --git a/libraries/pico_graphics/pico_graphics_pen_inky7.cpp b/libraries/pico_graphics/pico_graphics_pen_inky7.cpp index 9a6ae5b0d..e41de871d 100644 --- a/libraries/pico_graphics/pico_graphics_pen_inky7.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_inky7.cpp @@ -1,8 +1,8 @@ #include "pico_graphics.hpp" namespace pimoroni { - PicoGraphics_PenInky7::PicoGraphics_PenInky7(uint16_t width, uint16_t height, IDirectDisplayDriver &direct_display_driver) - : PicoGraphics(width, height, nullptr), + PicoGraphics_PenInky7::PicoGraphics_PenInky7(uint16_t width, uint16_t height, IDirectDisplayDriver &direct_display_driver, uint16_t layers) + : PicoGraphics(width, height, layers, nullptr), driver(direct_display_driver) { this->pen_type = PEN_INKY7; } diff --git a/libraries/pico_graphics/pico_graphics_pen_p4.cpp b/libraries/pico_graphics/pico_graphics_pen_p4.cpp index 68b3d62c2..c69deec79 100644 --- a/libraries/pico_graphics/pico_graphics_pen_p4.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_p4.cpp @@ -2,8 +2,8 @@ namespace pimoroni { - PicoGraphics_PenP4::PicoGraphics_PenP4(uint16_t width, uint16_t height, void *frame_buffer) - : PicoGraphics(width, height, frame_buffer) { + PicoGraphics_PenP4::PicoGraphics_PenP4(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers) + : PicoGraphics(width, height, layers, frame_buffer) { this->pen_type = PEN_P4; if(this->frame_buffer == nullptr) { this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); diff --git a/libraries/pico_graphics/pico_graphics_pen_p8.cpp b/libraries/pico_graphics/pico_graphics_pen_p8.cpp index f7d0be73d..b93f835bf 100644 --- a/libraries/pico_graphics/pico_graphics_pen_p8.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_p8.cpp @@ -1,8 +1,8 @@ #include "pico_graphics.hpp" namespace pimoroni { - PicoGraphics_PenP8::PicoGraphics_PenP8(uint16_t width, uint16_t height, void *frame_buffer) - : PicoGraphics(width, height, frame_buffer) { + PicoGraphics_PenP8::PicoGraphics_PenP8(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers) + : PicoGraphics(width, height, layers, frame_buffer) { this->pen_type = PEN_P8; if(this->frame_buffer == nullptr) { this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp index 1bce808fc..58afcae8a 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp @@ -2,11 +2,11 @@ #include namespace pimoroni { - PicoGraphics_PenRGB332::PicoGraphics_PenRGB332(uint16_t width, uint16_t height, void *frame_buffer) - : PicoGraphics(width, height, frame_buffer) { + PicoGraphics_PenRGB332::PicoGraphics_PenRGB332(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers) + : PicoGraphics(width, height, layers, frame_buffer) { this->pen_type = PEN_RGB332; if(this->frame_buffer == nullptr) { - this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); + this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height) * layers]); } } void PicoGraphics_PenRGB332::set_pen(uint c) { @@ -23,12 +23,14 @@ namespace pimoroni { } void PicoGraphics_PenRGB332::set_pixel(const Point &p) { uint8_t *buf = (uint8_t *)frame_buffer; + buf += buffer_size(this->bounds.w, this->bounds.h) * layer; buf[p.y * bounds.w + p.x] = color; } void PicoGraphics_PenRGB332::set_pixel_span(const Point &p, uint l) { // pointer to byte in framebuffer that contains this pixel uint8_t *buf = (uint8_t *)frame_buffer; - buf = &buf[p.y * bounds.w + p.x]; + buf += buffer_size(this->bounds.w, this->bounds.h) * layer; + buf += p.y * bounds.w + p.x; while(l--) { *buf++ = color; @@ -38,6 +40,7 @@ namespace pimoroni { if(!bounds.contains(p)) return; uint8_t *buf = (uint8_t *)frame_buffer; + buf += buffer_size(this->bounds.w, this->bounds.h) * layer; RGB332 blended = RGB(buf[p.y * bounds.w + p.x]).blend(RGB(color), a).to_rgb332(); @@ -96,9 +99,20 @@ namespace pimoroni { // Treat our void* frame_buffer as uint8_t uint8_t *src = (uint8_t *)frame_buffer; - frame_convert_rgb565(callback, [&]() { - return rgb332_to_rgb565_lut[*src++]; - }); + if(this->layers > 1) { + // Assume only two layers for now + uint8_t *src_layer2 = src + buffer_size(this->bounds.w, this->bounds.h); + + frame_convert_rgb565(callback, [&]() { + RGB565 c1 = rgb332_to_rgb565_lut[*src++]; + RGB565 c2 = rgb332_to_rgb565_lut[*src_layer2++]; + return c2 ? c2 : c1; + }); + } else { + frame_convert_rgb565(callback, [&]() { + return rgb332_to_rgb565_lut[*src++]; + }); + } } } void PicoGraphics_PenRGB332::sprite(void* data, const Point &sprite, const Point &dest, const int scale, const int transparent) { diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp index 30ef5bed6..fb7b37e38 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp @@ -1,8 +1,8 @@ #include "pico_graphics.hpp" namespace pimoroni { - PicoGraphics_PenRGB565::PicoGraphics_PenRGB565(uint16_t width, uint16_t height, void *frame_buffer) - : PicoGraphics(width, height, frame_buffer) { + PicoGraphics_PenRGB565::PicoGraphics_PenRGB565(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers) + : PicoGraphics(width, height, layers, frame_buffer) { this->pen_type = PEN_RGB565; if(this->frame_buffer == nullptr) { this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); @@ -23,11 +23,15 @@ namespace pimoroni { } void PicoGraphics_PenRGB565::set_pixel(const Point &p) { uint16_t *buf = (uint16_t *)frame_buffer; + // We can't use buffer_size because our pointer is uint16_t + buf += this->bounds.w * this->bounds.h * layer; buf[p.y * bounds.w + p.x] = color; } void PicoGraphics_PenRGB565::set_pixel_span(const Point &p, uint l) { // pointer to byte in framebuffer that contains this pixel uint16_t *buf = (uint16_t *)frame_buffer; + // We can't use buffer_size because our pointer is uint16_t + buf += this->bounds.w * this->bounds.h * layer; buf = &buf[p.y * bounds.w + p.x]; while(l--) { @@ -35,26 +39,50 @@ namespace pimoroni { } } + void PicoGraphics_PenRGB565::frame_convert(PenType type, conversion_callback_func callback) { + if(type == PEN_RGB565) { + // Treat our void* frame_buffer as uint8_t + uint16_t *src = (uint16_t *)frame_buffer; - void PicoGraphics_PenRGB565::sprite(void* data, const Point &sprite, const Point &dest, const int scale, const int transparent) { - //int sprite_x = (sprite & 0x0f) << 3; - //int sprite_y = (sprite & 0xf0) >> 1; - Point s { - sprite.x << 3, - sprite.y << 3 - }; - RGB565 *ptr = (RGB565 *)data; - Point o = {0, 0}; - for(o.y = 0; o.y < 8 * scale; o.y++) { - Point so = { - 0, - o.y / scale - }; - for(o.x = 0; o.x < 8 * scale; o.x++) { - so.x = o.x / scale; - color = ptr[(s.y + so.y) * 128 + (s.x + so.x)]; - if(color != transparent) pixel(dest + o); - } - } - } + if(layers > 1) { + // Assume only two layers for now + uint16_t *src_layer2 = src; + + // We can't use buffer_size because our pointer is uint16_t + src_layer2 += this->bounds.w * this->bounds.h * layer; + + frame_convert_rgb565(callback, [&]() { + RGB565 c1 = *src++; + RGB565 c2 = *src_layer2++; + return c2 ? c2 : c1; + }); + } else { + frame_convert_rgb565(callback, [&]() { + return *src++; + }); + } + } + } + + void PicoGraphics_PenRGB565::sprite(void* data, const Point &sprite, const Point &dest, const int scale, const int transparent) { + //int sprite_x = (sprite & 0x0f) << 3; + //int sprite_y = (sprite & 0xf0) >> 1; + Point s { + sprite.x << 3, + sprite.y << 3 + }; + RGB565 *ptr = (RGB565 *)data; + Point o = {0, 0}; + for(o.y = 0; o.y < 8 * scale; o.y++) { + Point so = { + 0, + o.y / scale + }; + for(o.x = 0; o.x < 8 * scale; o.x++) { + so.x = o.x / scale; + color = ptr[(s.y + so.y) * 128 + (s.x + so.x)]; + if(color != transparent) pixel(dest + o); + } + } + } } diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp index 6cc9d2adf..6f5360643 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp @@ -1,8 +1,8 @@ #include "pico_graphics.hpp" namespace pimoroni { - PicoGraphics_PenRGB888::PicoGraphics_PenRGB888(uint16_t width, uint16_t height, void *frame_buffer) - : PicoGraphics(width, height, frame_buffer) { + PicoGraphics_PenRGB888::PicoGraphics_PenRGB888(uint16_t width, uint16_t height, void *frame_buffer, uint16_t layers) + : PicoGraphics(width, height, layers, frame_buffer) { this->pen_type = PEN_RGB888; if(this->frame_buffer == nullptr) { this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); diff --git a/micropython/modules/picographics/picographics.c b/micropython/modules/picographics/picographics.c index 0074b55f0..ad6d0dc57 100644 --- a/micropython/modules/picographics/picographics.c +++ b/micropython/modules/picographics/picographics.c @@ -24,6 +24,9 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_create_pen_obj, 4, 4, ModPic MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_create_pen_hsv_obj, 4, 4, ModPicoGraphics_create_pen_hsv); MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_thickness_obj, ModPicoGraphics_set_thickness); +// Layers +MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_layer_obj, ModPicoGraphics_set_layer); + // Primitives MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_set_clip_obj, 5, 5, ModPicoGraphics_set_clip); MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics_remove_clip_obj, ModPicoGraphics_remove_clip); @@ -60,6 +63,8 @@ static const mp_rom_map_elem_t ModPicoGraphics_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_set_thickness), MP_ROM_PTR(&ModPicoGraphics_set_thickness_obj) }, { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&ModPicoGraphics_clear_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_layer), MP_ROM_PTR(&ModPicoGraphics_set_layer_obj) }, + { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&ModPicoGraphics_update_obj) }, { MP_ROM_QSTR(MP_QSTR_partial_update), MP_ROM_PTR(&ModPicoGraphics_partial_update_obj) }, { MP_ROM_QSTR(MP_QSTR_set_update_speed), MP_ROM_PTR(&ModPicoGraphics_set_update_speed_obj) }, diff --git a/micropython/modules/picographics/picographics.cpp b/micropython/modules/picographics/picographics.cpp index a55e8ad41..8ac300cf0 100644 --- a/micropython/modules/picographics/picographics.cpp +++ b/micropython/modules/picographics/picographics.cpp @@ -254,24 +254,24 @@ bool get_display_settings(PicoGraphicsDisplay display, int &width, int &height, return true; } -size_t get_required_buffer_size(PicoGraphicsPenType pen_type, uint width, uint height) { +size_t get_required_buffer_size(PicoGraphicsPenType pen_type, uint width, uint height, uint layers) { switch(pen_type) { case PEN_1BIT: - return PicoGraphics_Pen1Bit::buffer_size(width, height); + return PicoGraphics_Pen1Bit::buffer_size(width, height) * layers; case PEN_3BIT: - return PicoGraphics_Pen3Bit::buffer_size(width, height); + return PicoGraphics_Pen3Bit::buffer_size(width, height) * layers; case PEN_P4: - return PicoGraphics_PenP4::buffer_size(width, height); + return PicoGraphics_PenP4::buffer_size(width, height) * layers; case PEN_P8: - return PicoGraphics_PenP8::buffer_size(width, height); + return PicoGraphics_PenP8::buffer_size(width, height) * layers; case PEN_RGB332: - return PicoGraphics_PenRGB332::buffer_size(width, height); + return PicoGraphics_PenRGB332::buffer_size(width, height) * layers; case PEN_RGB565: - return PicoGraphics_PenRGB565::buffer_size(width, height); + return PicoGraphics_PenRGB565::buffer_size(width, height) * layers; case PEN_RGB888: - return PicoGraphics_PenRGB888::buffer_size(width, height); + return PicoGraphics_PenRGB888::buffer_size(width, height) * layers; case PEN_INKY7: - return PicoGraphics_PenInky7::buffer_size(width, height); + return PicoGraphics_PenInky7::buffer_size(width, height) * layers; default: return 0; } @@ -280,7 +280,7 @@ size_t get_required_buffer_size(PicoGraphicsPenType pen_type, uint width, uint h mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { ModPicoGraphics_obj_t *self = nullptr; - enum { ARG_display, ARG_rotate, ARG_bus, ARG_buffer, ARG_pen_type, ARG_extra_pins, ARG_i2c_address }; + enum { ARG_display, ARG_rotate, ARG_bus, ARG_buffer, ARG_pen_type, ARG_extra_pins, ARG_i2c_address, ARG_layers }; static const mp_arg_t allowed_args[] = { { MP_QSTR_display, MP_ARG_INT | MP_ARG_REQUIRED }, { MP_QSTR_rotate, MP_ARG_INT, { .u_int = -1 } }, @@ -289,6 +289,7 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size { MP_QSTR_pen_type, MP_ARG_INT, { .u_int = -1 } }, { MP_QSTR_extra_pins, MP_ARG_OBJ, { .u_obj = mp_const_none } }, { MP_QSTR_i2c_address, MP_ARG_INT, { .u_int = -1 } }, + { MP_QSTR_layers, MP_ARG_INT, { .u_int = 1 } }, }; // Parse args. @@ -304,6 +305,7 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size int height = 0; int pen_type = args[ARG_pen_type].u_int; int rotate = args[ARG_rotate].u_int; + int layers = args[ARG_layers].u_int; PicoGraphicsBusType bus_type = BUS_SPI; if(!get_display_settings(display, width, height, rotate, pen_type, bus_type)) mp_raise_ValueError("Unsupported display!"); if(rotate == -1) rotate = (int)Rotation::ROTATE_0; @@ -395,7 +397,7 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size } // Create or fetch buffer - size_t required_size = get_required_buffer_size((PicoGraphicsPenType)pen_type, width, height); + size_t required_size = get_required_buffer_size((PicoGraphicsPenType)pen_type, width, height, layers); if(required_size == 0) mp_raise_ValueError("Unsupported pen type!"); if(pen_type == PEN_INKY7) { @@ -418,31 +420,31 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size switch((PicoGraphicsPenType)pen_type) { case PEN_1BIT: if (display == DISPLAY_INKY_PACK) { - self->graphics = m_new_class(PicoGraphics_Pen1BitY, self->display->width, self->display->height, self->buffer); + self->graphics = m_new_class(PicoGraphics_Pen1BitY, self->display->width, self->display->height, self->buffer, layers); } else { - self->graphics = m_new_class(PicoGraphics_Pen1Bit, self->display->width, self->display->height, self->buffer); + self->graphics = m_new_class(PicoGraphics_Pen1Bit, self->display->width, self->display->height, self->buffer, layers); } break; case PEN_3BIT: - self->graphics = m_new_class(PicoGraphics_Pen3Bit, self->display->width, self->display->height, self->buffer); + self->graphics = m_new_class(PicoGraphics_Pen3Bit, self->display->width, self->display->height, self->buffer, layers); break; case PEN_P4: - self->graphics = m_new_class(PicoGraphics_PenP4, self->display->width, self->display->height, self->buffer); + self->graphics = m_new_class(PicoGraphics_PenP4, self->display->width, self->display->height, self->buffer, layers); break; case PEN_P8: - self->graphics = m_new_class(PicoGraphics_PenP8, self->display->width, self->display->height, self->buffer); + self->graphics = m_new_class(PicoGraphics_PenP8, self->display->width, self->display->height, self->buffer, layers); break; case PEN_RGB332: - self->graphics = m_new_class(PicoGraphics_PenRGB332, self->display->width, self->display->height, self->buffer); + self->graphics = m_new_class(PicoGraphics_PenRGB332, self->display->width, self->display->height, self->buffer, layers); break; case PEN_RGB565: - self->graphics = m_new_class(PicoGraphics_PenRGB565, self->display->width, self->display->height, self->buffer); + self->graphics = m_new_class(PicoGraphics_PenRGB565, self->display->width, self->display->height, self->buffer, layers); break; case PEN_RGB888: - self->graphics = m_new_class(PicoGraphics_PenRGB888, self->display->width, self->display->height, self->buffer); + self->graphics = m_new_class(PicoGraphics_PenRGB888, self->display->width, self->display->height, self->buffer, layers); break; case PEN_INKY7: - self->graphics = m_new_class(PicoGraphics_PenInky7, self->display->width, self->display->height, *(IDirectDisplayDriver *)self->buffer); + self->graphics = m_new_class(PicoGraphics_PenInky7, self->display->width, self->display->height, *(IDirectDisplayDriver *)self->buffer, layers); break; default: break; @@ -453,8 +455,15 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size self->spritedata = nullptr; // Clear the buffer + self->graphics->set_layer(0); self->graphics->set_pen(0); self->graphics->clear(); + if(layers > 1) { + self->graphics->set_layer(1); + self->graphics->set_pen(0); + self->graphics->clear(); + self->graphics->set_layer(0); + } // Update the LCD from the graphics library if (display != DISPLAY_INKY_FRAME && display != DISPLAY_INKY_FRAME_4 && display != DISPLAY_INKY_PACK && display != DISPLAY_INKY_FRAME_7) { @@ -477,7 +486,7 @@ mp_obj_t ModPicoGraphics_set_spritesheet(mp_obj_t self_in, mp_obj_t spritedata) mp_buffer_info_t bufinfo; mp_get_buffer_raise(spritedata, &bufinfo, MP_BUFFER_RW); - int required_size = get_required_buffer_size((PicoGraphicsPenType)self->graphics->pen_type, 128, 128); + int required_size = get_required_buffer_size((PicoGraphicsPenType)self->graphics->pen_type, 128, 128, 1); if(bufinfo.len != (size_t)(required_size)) { mp_raise_ValueError("Spritesheet the wrong size!"); @@ -562,7 +571,7 @@ mp_int_t ModPicoGraphics_get_framebuffer(mp_obj_t self_in, mp_buffer_info_t *buf mp_raise_ValueError("No local framebuffer."); } bufinfo->buf = self->graphics->frame_buffer; - bufinfo->len = get_required_buffer_size((PicoGraphicsPenType)self->graphics->pen_type, self->graphics->bounds.w, self->graphics->bounds.h); + bufinfo->len = get_required_buffer_size((PicoGraphicsPenType)self->graphics->pen_type, self->graphics->bounds.w, self->graphics->bounds.h, 1); bufinfo->typecode = 'B'; return 0; } @@ -603,7 +612,7 @@ mp_obj_t ModPicoGraphics_get_required_buffer_size(mp_obj_t display_in, mp_obj_t int pen_type = mp_obj_get_int(pen_type_in); PicoGraphicsBusType bus_type = BUS_SPI; if(!get_display_settings(display, width, height, rotation, pen_type, bus_type)) mp_raise_ValueError("Unsupported display!"); - size_t required_size = get_required_buffer_size((PicoGraphicsPenType)pen_type, width, height); + size_t required_size = get_required_buffer_size((PicoGraphicsPenType)pen_type, width, height, 1); if(required_size == 0) mp_raise_ValueError("Unsupported pen type!"); return mp_obj_new_int(required_size); @@ -755,6 +764,14 @@ mp_obj_t ModPicoGraphics_set_pen(mp_obj_t self_in, mp_obj_t pen) { return mp_const_none; } +mp_obj_t ModPicoGraphics_set_layer(mp_obj_t self_in, mp_obj_t layer) { + ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t); + + self->graphics->set_layer(mp_obj_get_int(layer)); + + return mp_const_none; +} + mp_obj_t ModPicoGraphics_reset_pen(mp_obj_t self_in, mp_obj_t pen) { ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t); diff --git a/micropython/modules/picographics/picographics.h b/micropython/modules/picographics/picographics.h index 655d336b7..1238354d9 100644 --- a/micropython/modules/picographics/picographics.h +++ b/micropython/modules/picographics/picographics.h @@ -77,6 +77,9 @@ extern mp_obj_t ModPicoGraphics_reset_pen(mp_obj_t self_in, mp_obj_t pen); extern mp_obj_t ModPicoGraphics_set_palette(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t ModPicoGraphics_hsv_to_rgb(size_t n_args, const mp_obj_t *args); +// Layers +extern mp_obj_t ModPicoGraphics_set_layer(mp_obj_t self_in, mp_obj_t layer); + // Pen extern mp_obj_t ModPicoGraphics_set_pen(mp_obj_t self_in, mp_obj_t pen); extern mp_obj_t ModPicoGraphics_create_pen(size_t n_args, const mp_obj_t *args); From d528c6ff1b42d5ccabbe71d5759cf3d4daf46dc9 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 7 Oct 2024 15:51:02 +0100 Subject: [PATCH 02/35] PicoGraphics: Support multiple layers in more types. --- drivers/st7735/st7735.cpp | 22 ++--- drivers/st7789/st7789.cpp | 38 +++++---- libraries/pico_graphics/pico_graphics.cpp | 1 + libraries/pico_graphics/pico_graphics.hpp | 2 + .../pico_graphics/pico_graphics_pen_p4.cpp | 45 ++++++++--- .../pico_graphics/pico_graphics_pen_p8.cpp | 80 +++++++++++++++---- .../pico_graphics_pen_rgb332.cpp | 27 ++++--- .../pico_graphics_pen_rgb565.cpp | 8 +- .../pico_graphics_pen_rgb888.cpp | 2 + 9 files changed, 158 insertions(+), 67 deletions(-) diff --git a/drivers/st7735/st7735.cpp b/drivers/st7735/st7735.cpp index 1ae5728eb..b945537a8 100644 --- a/drivers/st7735/st7735.cpp +++ b/drivers/st7735/st7735.cpp @@ -165,17 +165,21 @@ namespace pimoroni { // Native 16-bit framebuffer update void ST7735::update(PicoGraphics *graphics) { - command(reg::RAMWR); - gpio_put(dc, 1); // data mode - gpio_put(cs, 0); + if(graphics->pen_type == PicoGraphics::PEN_RGB565 && graphics->layers == 1) { + command(reg::RAMWR, width * height * sizeof(uint16_t), (const char*)graphics->frame_buffer); + } else { + command(reg::RAMWR); + gpio_put(dc, 1); // data mode + gpio_put(cs, 0); - graphics->frame_convert(PicoGraphics::PEN_RGB565, [this](void *data, size_t length) { - if (length > 0) { - spi_write_blocking(spi, (const uint8_t*)data, length); - } - }); + graphics->frame_convert(PicoGraphics::PEN_RGB565, [this](void *data, size_t length) { + if (length > 0) { + spi_write_blocking(spi, (const uint8_t*)data, length); + } + }); - gpio_put(cs, 1); + gpio_put(cs, 1); + } } void ST7735::set_backlight(uint8_t brightness) { diff --git a/drivers/st7789/st7789.cpp b/drivers/st7789/st7789.cpp index b76f9b078..9f033adc5 100644 --- a/drivers/st7789/st7789.cpp +++ b/drivers/st7789/st7789.cpp @@ -282,26 +282,30 @@ namespace pimoroni { void ST7789::update(PicoGraphics *graphics) { uint8_t cmd = reg::RAMWR; - gpio_put(dc, 0); // command mode - gpio_put(cs, 0); - if(spi) { // SPI Bus - spi_write_blocking(spi, &cmd, 1); - } else { // Parallel Bus - write_blocking_parallel(&cmd, 1); - } + if(graphics->pen_type == PicoGraphics::PEN_RGB565 && graphics->layers == 1) { // Display buffer is screen native + command(cmd, width * height * sizeof(uint16_t), (const char*)graphics->frame_buffer); + } else { + gpio_put(dc, 0); // command mode + gpio_put(cs, 0); + if(spi) { // SPI Bus + spi_write_blocking(spi, &cmd, 1); + } else { // Parallel Bus + write_blocking_parallel(&cmd, 1); + } - gpio_put(dc, 1); // data mode + gpio_put(dc, 1); // data mode - graphics->frame_convert(PicoGraphics::PEN_RGB565, [this](void *data, size_t length) { - if (length > 0) { - write_blocking_dma((const uint8_t*)data, length); - } - else { - dma_channel_wait_for_finish_blocking(st_dma); - } - }); + graphics->frame_convert(PicoGraphics::PEN_RGB565, [this](void *data, size_t length) { + if (length > 0) { + write_blocking_dma((const uint8_t*)data, length); + } + else { + dma_channel_wait_for_finish_blocking(st_dma); + } + }); - gpio_put(cs, 1); + gpio_put(cs, 1); + } } void ST7789::set_backlight(uint8_t brightness) { diff --git a/libraries/pico_graphics/pico_graphics.cpp b/libraries/pico_graphics/pico_graphics.cpp index 58ae1ba70..c457ca99d 100644 --- a/libraries/pico_graphics/pico_graphics.cpp +++ b/libraries/pico_graphics/pico_graphics.cpp @@ -21,6 +21,7 @@ namespace pimoroni { void PicoGraphics::set_layer(uint l) { this->layer = l; + this->layer_offset = this->bounds.w * this->bounds.h * l; }; uint PicoGraphics::get_layer() { return this->layer; diff --git a/libraries/pico_graphics/pico_graphics.hpp b/libraries/pico_graphics/pico_graphics.hpp index 66d74254c..c16f58f35 100644 --- a/libraries/pico_graphics/pico_graphics.hpp +++ b/libraries/pico_graphics/pico_graphics.hpp @@ -79,6 +79,7 @@ namespace pimoroni { } } + constexpr operator bool() {return r || g || b;}; constexpr RGB operator+ (const RGB& c) const {return RGB(r + c.r, g + c.g, b + c.b);} constexpr RGB& operator+=(const RGB& c) {r += c.r; g += c.g; b += c.b; return *this;} constexpr RGB& operator-=(const RGB& c) {r -= c.r; g -= c.g; b -= c.b; return *this;} @@ -228,6 +229,7 @@ namespace pimoroni { uint layers = 1; uint layer = 0; + uint layer_offset = 0; typedef std::function conversion_callback_func; typedef std::function next_pixel_func; diff --git a/libraries/pico_graphics/pico_graphics_pen_p4.cpp b/libraries/pico_graphics/pico_graphics_pen_p4.cpp index c69deec79..bf5edc3fc 100644 --- a/libraries/pico_graphics/pico_graphics_pen_p4.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_p4.cpp @@ -59,6 +59,7 @@ namespace pimoroni { // pointer to byte in framebuffer that contains this pixel uint8_t *buf = (uint8_t *)frame_buffer; + buf += this->layer_offset / 2; uint8_t *f = &buf[i / 2]; uint8_t o = (~i & 0b1) * 4; // bit offset within byte @@ -74,6 +75,7 @@ namespace pimoroni { // pointer to byte in framebuffer that contains this pixel uint8_t *buf = (uint8_t *)frame_buffer; + buf += this->layer_offset / 2; uint8_t *f = &buf[i / 2]; // doubled up color value, so the color is stored in both nibbles @@ -144,16 +146,39 @@ namespace pimoroni { uint8_t *src = (uint8_t *)frame_buffer; uint8_t o = 4; - frame_convert_rgb565(callback, [&]() { - uint8_t c = *src; - uint8_t b = (c >> o) & 0xf; // bit value shifted to position - - // Increment to next 4-bit entry - o ^= 4; - if (o != 0) ++src; - - return cache[b]; - }); + if(this->layers > 1) { + + uint offset = this->bounds.w * this->bounds.h / 2; + + frame_convert_rgb565(callback, [&]() { + uint8_t b = 0; + + // Iterate through layers in reverse order + // Return the first nonzero (not transparent) pixel + for(auto layer = this->layers; layer > 0; layer--) { + uint8_t c = *(src + offset * (layer - 1)); + b = (c >> o) & 0xf; // bit value shifted to position + if (b) break; + } + + // Increment to next 4-bit entry + o ^= 4; + if (o != 0) src++; + + return cache[b]; + }); + } else { + frame_convert_rgb565(callback, [&]() { + uint8_t c = *src; + uint8_t b = (c >> o) & 0xf; // bit value shifted to position + + // Increment to next 4-bit entry + o ^= 4; + if (o != 0) ++src; + + return cache[b]; + }); + } } } } diff --git a/libraries/pico_graphics/pico_graphics_pen_p8.cpp b/libraries/pico_graphics/pico_graphics_pen_p8.cpp index b93f835bf..fd952690f 100644 --- a/libraries/pico_graphics/pico_graphics_pen_p8.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_p8.cpp @@ -51,12 +51,14 @@ namespace pimoroni { } void PicoGraphics_PenP8::set_pixel(const Point &p) { uint8_t *buf = (uint8_t *)frame_buffer; + buf += this->layer_offset; buf[p.y * bounds.w + p.x] = color; } void PicoGraphics_PenP8::set_pixel_span(const Point &p, uint l) { // pointer to byte in framebuffer that contains this pixel uint8_t *buf = (uint8_t *)frame_buffer; + buf += this->layer_offset; buf = &buf[p.y * bounds.w + p.x]; while(l--) { @@ -103,26 +105,70 @@ namespace pimoroni { } void PicoGraphics_PenP8::frame_convert(PenType type, conversion_callback_func callback) { - if(type == PEN_RGB565) { - // Cache the RGB888 palette as RGB565 - RGB565 cache[palette_size]; - for(auto i = 0u; i < palette_size; i++) { - cache[i] = palette[i].to_rgb565(); - } + // Treat our void* frame_buffer as uint8_t + uint8_t *src = (uint8_t *)frame_buffer; + + if(layers > 1) { + // The size of a single layer + uint offset = this->bounds.w * this->bounds.h; + + if(type == PEN_RGB565) { + // Cache the RGB888 palette as RGB565 + RGB565 cache[palette_size]; + for(auto i = 0u; i < palette_size; i++) { + cache[i] = palette[i].to_rgb565(); + } + + frame_convert_rgb565(callback, [&]() { + // Check the *palette* index, rather than the colour + // Thus palette entry 0 is *always* transparent + uint8_t c = 0; + + // Iterate through layers in reverse order + // Return the first nonzero (not transparent) pixel + for(auto layer = this->layers; layer > 0; layer--) { + c = *(src + offset * (layer - 1)); + if (c) break; + } - // Treat our void* frame_buffer as uint8_t - uint8_t *src = (uint8_t *)frame_buffer; + src++; - frame_convert_rgb565(callback, [&]() { - return cache[*src++]; - }); - } else if (type == PEN_RGB888) { - // Treat our void* frame_buffer as uint8_t - uint8_t *src = (uint8_t *)frame_buffer; + return cache[c]; + }); + } else if (type == PEN_RGB888) { + frame_convert_rgb888(callback, [&]() { + // Check the *palette* index, rather than the colour + // Thus palette entry 0 is *always* transparent + uint8_t c = 0; - frame_convert_rgb888(callback, [&]() { - return palette[*src++].to_rgb888(); - }); + // Iterate through layers in reverse order + // Return the first nonzero (not transparent) pixel + for(auto layer = this->layers; layer > 0; layer--) { + c = *(src + offset * (layer - 1)); + if (c) break; + } + + src++; + + return palette[c].to_rgb888(); + }); + } + } else { + if(type == PEN_RGB565) { + // Cache the RGB888 palette as RGB565 + RGB565 cache[palette_size]; + for(auto i = 0u; i < palette_size; i++) { + cache[i] = palette[i].to_rgb565(); + } + + frame_convert_rgb565(callback, [&]() { + return cache[*src++]; + }); + } else if (type == PEN_RGB888) { + frame_convert_rgb888(callback, [&]() { + return palette[*src++].to_rgb888(); + }); + } } } } diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp index 58afcae8a..3c8ca8521 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp @@ -23,13 +23,13 @@ namespace pimoroni { } void PicoGraphics_PenRGB332::set_pixel(const Point &p) { uint8_t *buf = (uint8_t *)frame_buffer; - buf += buffer_size(this->bounds.w, this->bounds.h) * layer; + buf += this->layer_offset; buf[p.y * bounds.w + p.x] = color; } void PicoGraphics_PenRGB332::set_pixel_span(const Point &p, uint l) { // pointer to byte in framebuffer that contains this pixel uint8_t *buf = (uint8_t *)frame_buffer; - buf += buffer_size(this->bounds.w, this->bounds.h) * layer; + buf += this->layer_offset; buf += p.y * bounds.w + p.x; while(l--) { @@ -40,7 +40,7 @@ namespace pimoroni { if(!bounds.contains(p)) return; uint8_t *buf = (uint8_t *)frame_buffer; - buf += buffer_size(this->bounds.w, this->bounds.h) * layer; + buf += this->layer_offset; RGB332 blended = RGB(buf[p.y * bounds.w + p.x]).blend(RGB(color), a).to_rgb332(); @@ -99,14 +99,23 @@ namespace pimoroni { // Treat our void* frame_buffer as uint8_t uint8_t *src = (uint8_t *)frame_buffer; - if(this->layers > 1) { - // Assume only two layers for now - uint8_t *src_layer2 = src + buffer_size(this->bounds.w, this->bounds.h); + if(this->layers > 1) { + // The size of a single layer + uint offset = this->bounds.w * this->bounds.h; frame_convert_rgb565(callback, [&]() { - RGB565 c1 = rgb332_to_rgb565_lut[*src++]; - RGB565 c2 = rgb332_to_rgb565_lut[*src_layer2++]; - return c2 ? c2 : c1; + uint8_t c = 0; + + // Iterate through layers in reverse order + // Return the first nonzero (not transparent) pixel + for(auto layer = this->layers; layer > 0; layer--) { + c = *(src + offset * (layer - 1)); + if (c) break; + } + + src++; + + return rgb332_to_rgb565_lut[c]; }); } else { frame_convert_rgb565(callback, [&]() { diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp index fb7b37e38..b2e21bd7c 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp @@ -24,14 +24,14 @@ namespace pimoroni { void PicoGraphics_PenRGB565::set_pixel(const Point &p) { uint16_t *buf = (uint16_t *)frame_buffer; // We can't use buffer_size because our pointer is uint16_t - buf += this->bounds.w * this->bounds.h * layer; + buf += this->layer_offset; buf[p.y * bounds.w + p.x] = color; } void PicoGraphics_PenRGB565::set_pixel_span(const Point &p, uint l) { // pointer to byte in framebuffer that contains this pixel uint16_t *buf = (uint16_t *)frame_buffer; // We can't use buffer_size because our pointer is uint16_t - buf += this->bounds.w * this->bounds.h * layer; + buf += this->layer_offset; buf = &buf[p.y * bounds.w + p.x]; while(l--) { @@ -46,10 +46,8 @@ namespace pimoroni { if(layers > 1) { // Assume only two layers for now - uint16_t *src_layer2 = src; - // We can't use buffer_size because our pointer is uint16_t - src_layer2 += this->bounds.w * this->bounds.h * layer; + uint16_t *src_layer2 = src + this->bounds.w * this->bounds.h; frame_convert_rgb565(callback, [&]() { RGB565 c1 = *src++; diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp index 6f5360643..0b145239b 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp @@ -23,11 +23,13 @@ namespace pimoroni { } void PicoGraphics_PenRGB888::set_pixel(const Point &p) { uint32_t *buf = (uint32_t *)frame_buffer; + buf += this->layer_offset; buf[p.y * bounds.w + p.x] = color; } void PicoGraphics_PenRGB888::set_pixel_span(const Point &p, uint l) { // pointer to byte in framebuffer that contains this pixel uint32_t *buf = (uint32_t *)frame_buffer; + buf += this->layer_offset; buf = &buf[p.y * bounds.w + p.x]; while(l--) { From 6c136e0be87c81bc5380157b4a821ad51f88ba02 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 26 Sep 2023 13:37:55 +0100 Subject: [PATCH 03/35] PicoVector: Rewrite around new C pretty-poly.h. --- .gitmodules | 3 + examples/CMakeLists.txt | 1 + examples/pico_w_explorer/CMakeLists.txt | 1 + .../pico_w_explorer_vector.cmake | 10 + .../pico_w_explorer_vector.cpp | 51 +++ libraries/CMakeLists.txt | 1 + libraries/pico_vector/CMakeLists.txt | 1 + libraries/pico_vector/alright_fonts.cpp | 65 +--- libraries/pico_vector/alright_fonts.hpp | 19 +- libraries/pico_vector/file_io.hpp | 17 + libraries/pico_vector/pico_vector.cmake | 7 +- libraries/pico_vector/pico_vector.cpp | 185 ++++------ libraries/pico_vector/pico_vector.hpp | 109 +++--- libraries/pico_vector/pretty_poly | 1 + libraries/pico_vector/pretty_poly.cpp | 339 ------------------ libraries/pico_vector/pretty_poly.hpp | 73 ---- libraries/pico_vector/pretty_poly_types.hpp | 162 --------- 17 files changed, 237 insertions(+), 808 deletions(-) create mode 100644 examples/pico_w_explorer/CMakeLists.txt create mode 100644 examples/pico_w_explorer/pico_w_explorer_vector.cmake create mode 100644 examples/pico_w_explorer/pico_w_explorer_vector.cpp create mode 100644 libraries/pico_vector/CMakeLists.txt create mode 100644 libraries/pico_vector/file_io.hpp create mode 160000 libraries/pico_vector/pretty_poly delete mode 100644 libraries/pico_vector/pretty_poly.cpp delete mode 100644 libraries/pico_vector/pretty_poly.hpp delete mode 100644 libraries/pico_vector/pretty_poly_types.hpp diff --git a/.gitmodules b/.gitmodules index b1f258d23..4861dad5d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "drivers/mlx90640/src"] path = drivers/mlx90640/src url = https://github.com/melexis/mlx90640-library +[submodule "libraries/pico_vector/pretty_poly"] + path = libraries/pico_vector/pretty_poly + url = https://github.com/lowfatcode/pretty-poly/ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c1f910d78..93e55d863 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -63,3 +63,4 @@ add_subdirectory(galactic_unicorn) add_subdirectory(gfx_pack) add_subdirectory(cosmic_unicorn) add_subdirectory(stellar_unicorn) +add_subdirectory(pico_w_explorer) diff --git a/examples/pico_w_explorer/CMakeLists.txt b/examples/pico_w_explorer/CMakeLists.txt new file mode 100644 index 000000000..6d8df5d19 --- /dev/null +++ b/examples/pico_w_explorer/CMakeLists.txt @@ -0,0 +1 @@ +include(pico_w_explorer_vector.cmake) \ No newline at end of file diff --git a/examples/pico_w_explorer/pico_w_explorer_vector.cmake b/examples/pico_w_explorer/pico_w_explorer_vector.cmake new file mode 100644 index 000000000..1e8b09314 --- /dev/null +++ b/examples/pico_w_explorer/pico_w_explorer_vector.cmake @@ -0,0 +1,10 @@ +add_executable( + pico_w_explorer_vector + pico_w_explorer_vector.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(pico_w_explorer_vector pico_stdlib pico_graphics pico_vector st7789) + +# create map/bin/hex file etc. +pico_add_extra_outputs(pico_w_explorer_vector) diff --git a/examples/pico_w_explorer/pico_w_explorer_vector.cpp b/examples/pico_w_explorer/pico_w_explorer_vector.cpp new file mode 100644 index 000000000..af8e3ec50 --- /dev/null +++ b/examples/pico_w_explorer/pico_w_explorer_vector.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +#include "drivers/st7789/st7789.hpp" +#include "libraries/pico_graphics/pico_graphics.hpp" +#include "libraries/pico_vector/pico_vector.hpp" + +using namespace pimoroni; + + +ST7789 st7789(320, 240, ROTATE_0, false, {PIMORONI_SPI_DEFAULT_INSTANCE, 17, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, PIN_UNUSED, SPI_DEFAULT_MISO, 9}); +PicoGraphics_PenRGB332 graphics(st7789.width, st7789.height, nullptr); +PicoVector vector(&graphics); + +int main() { + st7789.set_backlight(255); + + Pen WHITE = graphics.create_pen(255, 255, 255); + Pen BLACK = graphics.create_pen(0, 0, 0); + + while(true) { + graphics.set_pen(BLACK); + graphics.clear(); + + graphics.set_pen(WHITE); + graphics.text("Hello World", Point(0, 0), 320); + + pp_point_t outline[] = {{-128, -128}, {128, -128}, {128, 128}, {-128, 128}}; + pp_point_t hole[] = {{ -64, 64}, { 64, 64}, { 64, -64}, { -64, -64}}; + pp_path_t paths[] = { + {.points = outline, .count = 4}, + {.points = hole, .count = 4} + }; + pp_poly_t poly = {.paths = paths, .count = 2}; + + vector.rotate(&poly, {0, 0}, 45.0f); + vector.translate(&poly, {128, 128}); + + vector.draw(&poly); + + pp_mat3_t t = pp_mat3_identity(); + vector.text("Hello World", {0, 0}, &t); + + // update screen + st7789.update(&graphics); + } + + return 0; +} diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index e6d140adf..0ecbdde00 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -44,3 +44,4 @@ add_subdirectory(gfx_pack) add_subdirectory(interstate75) add_subdirectory(cosmic_unicorn) add_subdirectory(stellar_unicorn) +add_subdirectory(pico_vector) diff --git a/libraries/pico_vector/CMakeLists.txt b/libraries/pico_vector/CMakeLists.txt new file mode 100644 index 000000000..c2f446da7 --- /dev/null +++ b/libraries/pico_vector/CMakeLists.txt @@ -0,0 +1 @@ +include(pico_vector.cmake) diff --git a/libraries/pico_vector/alright_fonts.cpp b/libraries/pico_vector/alright_fonts.cpp index a679c97b9..b276b1ee6 100644 --- a/libraries/pico_vector/alright_fonts.cpp +++ b/libraries/pico_vector/alright_fonts.cpp @@ -8,13 +8,11 @@ #include "alright_fonts.hpp" -using namespace pretty_poly; - namespace alright_fonts { /* utility functions */ - pretty_poly::rect_t measure_character(text_metrics_t &tm, uint16_t codepoint) { + pp_rect_t measure_character(text_metrics_t &tm, uint16_t codepoint) { if(tm.face.glyphs.count(codepoint) == 1) { glyph_t glyph = tm.face.glyphs[codepoint]; @@ -28,59 +26,13 @@ namespace alright_fonts { render functions */ - void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t origin) { + void render_character(text_metrics_t &tm, uint16_t codepoint, pp_point_t origin, pp_mat3_t *transform) { if(tm.face.glyphs.count(codepoint) == 1) { glyph_t glyph = tm.face.glyphs[codepoint]; - - // scale is a fixed point 16:16 value, our font data is already scaled to - // -128..127 so to get the pixel size we want we can just shift the - // users requested size up one bit - unsigned scale = tm.size << 9; - - pretty_poly::draw_polygon(glyph.contours, origin, scale); - } - } - - template - void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t origin, mat_t transform) { - if(tm.face.glyphs.count(codepoint) == 1) { - glyph_t glyph = tm.face.glyphs[codepoint]; - - // scale is a fixed point 16:16 value, our font data is already scaled to - // -128..127 so to get the pixel size we want we can just shift the - // users requested size up one bit - unsigned scale = tm.size << 9; - - std::vector> contours; - contours.reserve(glyph.contours.size()); - - unsigned int total_points = 0; - for(auto i = 0u; i < glyph.contours.size(); i++) { - total_points += glyph.contours[i].count;; - } - - point_t *points = (point_t *)malloc(sizeof(point_t) * total_points); - - for(auto i = 0u; i < glyph.contours.size(); i++) { - const unsigned int count = glyph.contours[i].count; - for(auto j = 0u; j < count; j++) { - point_t point(glyph.contours[i].points[j].x, glyph.contours[i].points[j].y); - point *= transform; - points[j] = point_t(point.x, point.y); - } - contours.emplace_back(points, count); - points += count; - } - - pretty_poly::draw_polygon(contours, origin, scale); - - free(contours[0].points); + pp_transform(transform); + pp_render(&glyph.contours); } } - - template void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t origin, pretty_poly::mat3_t transform); - template void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t origin, pretty_poly::mat2_t transform); - /* load functions */ @@ -124,6 +76,8 @@ namespace alright_fonts { g.bounds.h = ru8(ifs); g.advance = ru8(ifs); + g.contours.paths = (pp_path_t *)malloc(sizeof(pp_path_t) * 10); + if(ifs.fail()) { // could not read glyph dictionary entry return false; @@ -148,10 +102,11 @@ namespace alright_fonts { // allocate space to store point data for contour and read // from file - pretty_poly::point_t *points = new pretty_poly::point_t[count]; - ifs.read((char *)points, count * 2); + g.contours.paths[g.contours.count].points = (pp_point_t *)malloc(sizeof(pp_point_t) * count); + + ifs.read((char *)g.contours.paths[g.contours.count].points, sizeof(pp_point_t) * count); - g.contours.push_back({points, count}); + g.contours.count ++; } // return back to position in dictionary diff --git a/libraries/pico_vector/alright_fonts.hpp b/libraries/pico_vector/alright_fonts.hpp index 0590a59c9..65996f2b8 100644 --- a/libraries/pico_vector/alright_fonts.hpp +++ b/libraries/pico_vector/alright_fonts.hpp @@ -6,15 +6,16 @@ #include #include -#include "pretty_poly.hpp" +#include "pretty_poly/pretty-poly.h" +#include "file_io.hpp" namespace alright_fonts { struct glyph_t { uint16_t codepoint; - pretty_poly::rect_t bounds; + pp_rect_t bounds; uint8_t advance; - std::vector> contours; + pp_poly_t contours; }; struct face_t { @@ -23,10 +24,10 @@ namespace alright_fonts { std::map glyphs; face_t() {}; - face_t(pretty_poly::file_io &ifs) {load(ifs);} + face_t(file_io &ifs) {load(ifs);} face_t(std::string_view path) {load(path);} - bool load(pretty_poly::file_io &ifs); + bool load(file_io &ifs); bool load(std::string_view path); }; @@ -48,7 +49,7 @@ namespace alright_fonts { int word_spacing; // spacing between words alignment_t align; // horizontal and vertical alignment //optional transform; // arbitrary transformation - pretty_poly::antialias_t antialiasing = pretty_poly::X4; // level of antialiasing to apply + pp_antialias_t antialiasing = PP_AA_X4; // level of antialiasing to apply void set_size(int s) { size = s; @@ -63,13 +64,11 @@ namespace alright_fonts { /* utility functions */ - pretty_poly::rect_t measure_character(text_metrics_t &tm, uint16_t codepoint); + pp_rect_t measure_character(text_metrics_t &tm, uint16_t codepoint); /* render functions */ - void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t origin); - template - void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t origin, mat_t transform); + void render_character(text_metrics_t &tm, uint16_t codepoint, pp_point_t origin, pp_mat3_t *transform); } \ No newline at end of file diff --git a/libraries/pico_vector/file_io.hpp b/libraries/pico_vector/file_io.hpp new file mode 100644 index 000000000..cc92b8917 --- /dev/null +++ b/libraries/pico_vector/file_io.hpp @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +class file_io { +private: + void *state; + size_t filesize = 0; + +public: + file_io(std::string_view path); + ~file_io(); + size_t seek(size_t pos); + size_t read(void *buf, size_t len); + size_t tell(); + bool fail(); +}; \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.cmake b/libraries/pico_vector/pico_vector.cmake index 92a168d2b..c9056440b 100644 --- a/libraries/pico_vector/pico_vector.cmake +++ b/libraries/pico_vector/pico_vector.cmake @@ -1,9 +1,12 @@ +if(NOT TARGET pico_graphics) + include(${CMAKE_CURRENT_LIST_DIR}/../pico_graphics/pico_graphics.cmake) +endif() + add_library(pico_vector ${CMAKE_CURRENT_LIST_DIR}/pico_vector.cpp - ${CMAKE_CURRENT_LIST_DIR}/pretty_poly.cpp ${CMAKE_CURRENT_LIST_DIR}/alright_fonts.cpp ) target_include_directories(pico_vector INTERFACE ${CMAKE_CURRENT_LIST_DIR}) -target_link_libraries(pico_vector pico_stdlib hardware_interp) \ No newline at end of file +target_link_libraries(pico_vector pico_graphics pico_stdlib hardware_interp) \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp index 5c36238d7..4a609f32b 100644 --- a/libraries/pico_vector/pico_vector.cpp +++ b/libraries/pico_vector/pico_vector.cpp @@ -1,141 +1,82 @@ +#define PP_IMPLEMENTATION #include "pico_vector.hpp" #include namespace pimoroni { - void PicoVector::polygon(std::vector> contours, Point origin, int scale) { - pretty_poly::settings::clip = {graphics->clip.x, graphics->clip.y, graphics->clip.w, graphics->clip.h}; - pretty_poly::draw_polygon( - contours, - pretty_poly::point_t(origin.x, origin.y), - scale); - } + PicoGraphics *PicoVector::graphics = nullptr; - void PicoVector::rotate(std::vector> &contours, Point origin, float angle) { - pretty_poly::point_t t{(picovector_point_type)origin.x, (picovector_point_type)origin.y}; - angle = (2 * (float)M_PI / 360.f) * angle; - pretty_poly::mat2_t r = pretty_poly::mat2_t::rotation(angle); - for(auto &contour : contours) { - for(auto i = 0u; i < contour.count; i++) { - contour.points[i] -= t; - contour.points[i] *= r; - contour.points[i] += t; - } - } + void PicoVector::draw(pp_poly_t *poly) { + pp_transform(NULL); + pp_render(poly); } - void PicoVector::translate(std::vector> &contours, Point translation) { - pretty_poly::point_t t{(picovector_point_type)translation.x, (picovector_point_type)translation.y}; - for(auto &contour : contours) { - for(auto i = 0u; i < contour.count; i++) { - contour.points[i] += t; - } - } + void PicoVector::draw(pp_poly_t *poly, pp_mat3_t *t) { + pp_transform(t); + pp_render(poly); } - void PicoVector::rotate(pretty_poly::contour_t &contour, Point origin, float angle) { - pretty_poly::point_t t{(picovector_point_type)origin.x, (picovector_point_type)origin.y}; - angle = (2 * (float)M_PI / 360.f) * angle; - pretty_poly::mat2_t r = pretty_poly::mat2_t::rotation(angle); - for(auto i = 0u; i < contour.count; i++) { - contour.points[i] -= t; - contour.points[i] *= r; - contour.points[i] += t; - } + 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); + transform(path, &t); } - void PicoVector::translate(pretty_poly::contour_t &contour, Point translation) { - pretty_poly::point_t t{(picovector_point_type)translation.x, (picovector_point_type)translation.y}; - for(auto i = 0u; i < contour.count; i++) { - contour.points[i] += t; - } + void PicoVector::translate(pp_path_t *path, pp_point_t translation) { + pp_mat3_t t = pp_mat3_identity(); + pp_mat3_translate(&t, translation.x, translation.y); + transform(path, &t); } - Point PicoVector::text(std::string_view text, Point origin) { - // Copy clipping bounds from the PicoGraphics instance - pretty_poly::settings::clip = {graphics->clip.x, graphics->clip.y, graphics->clip.w, graphics->clip.h}; - // TODO: Normalize types somehow, so we're not converting? - pretty_poly::point_t caret = pretty_poly::point_t(origin.x, origin.y); - - // Align text from the bottom left - caret.y += text_metrics.size; - - int16_t space_width = alright_fonts::measure_character(text_metrics, ' ').w; - if (space_width == 0) { - space_width = text_metrics.word_spacing; + void PicoVector::transform(pp_path_t *path, pp_mat3_t *t) { + for (auto j = 0u; j < path->count; j++) { + path->points[j] = pp_point_transform(&path->points[j], t); } + } - size_t i = 0; - - while(i < text.length()) { - size_t next_space = text.find(' ', i + 1); - - if(next_space == std::string::npos) { - next_space = text.length(); - } - - size_t next_linebreak = text.find('\n', i + 1); - - if(next_linebreak == std::string::npos) { - next_linebreak = text.length(); - } - - size_t next_break = std::min(next_space, next_linebreak); - - 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 += text_metrics.letter_spacing; - } - - if(caret.x != 0 && caret.x + word_width > graphics->clip.w) { - caret.x = origin.x; - caret.y += text_metrics.line_height; - } + 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); + transform(poly, &t); + } - for(size_t j = i; j < std::min(next_break + 1, text.length()); j++) { - if (text[j] == '\n') { // Linebreak - caret.x = origin.x; - caret.y += text_metrics.line_height; - } else if (text[j] == ' ') { // Space - caret.x += space_width; - } else { - alright_fonts::render_character(text_metrics, text[j], caret); - } - caret.x += alright_fonts::measure_character(text_metrics, text[j]).w; - caret.x += text_metrics.letter_spacing; - } + void PicoVector::translate(pp_poly_t *poly, pp_point_t translation) { + pp_mat3_t t = pp_mat3_identity(); + pp_mat3_translate(&t, translation.x, translation.y); + transform(poly, &t); + } - i = next_break + 1; + void PicoVector::transform(pp_poly_t *poly, pp_mat3_t *t) { + for (auto i = 0u; i < poly->count; i++) { + transform(&poly->paths[i], t); } - - return Point(caret.x, caret.y); } - Point PicoVector::text(std::string_view text, Point origin, float angle) { - // Copy clipping bounds from the PicoGraphics instance - pretty_poly::settings::clip = {graphics->clip.x, graphics->clip.y, graphics->clip.w, graphics->clip.h}; - // TODO: Normalize types somehow, so we're not converting? - pretty_poly::point_t caret(0, 0); - - // Prepare a transformation matrix for character and offset rotation - angle = (2 * (float)M_PI / 360.f) * angle; - pretty_poly::mat2_t transform = pretty_poly::mat2_t::rotation(angle); + pp_point_t PicoVector::text(std::string_view text, pp_point_t offset, pp_mat3_t *t) { + pp_point_t caret = {0, 0}; // Align text from the bottom left - caret.y += text_metrics.line_height; - caret *= transform; + caret.y += (PP_COORD_TYPE)text_metrics.line_height; + + caret = pp_point_transform(&caret, t); + caret.x += offset.x; + caret.y += offset.y; - pretty_poly::point_t space; - pretty_poly::point_t carriage_return(0, -text_metrics.line_height); + 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; if (space.x == 0) { space.x = text_metrics.word_spacing; } - space *= transform; - carriage_return *= transform; - const pretty_poly::point_t initial_carriage_return = carriage_return; + space = pp_point_transform(&space, t); + carriage_return = pp_point_transform(&carriage_return, t); + + pp_point_t initial_carriage_return = carriage_return; size_t i = 0; @@ -161,32 +102,32 @@ namespace pimoroni { } if(caret.x != 0 && caret.x + word_width > graphics->clip.w) { - caret -= carriage_return; + caret = pp_point_sub(&caret, &carriage_return); carriage_return = initial_carriage_return; } for(size_t j = i; j < std::min(next_break + 1, text.length()); j++) { if (text[j] == '\n') { // Linebreak - caret -= carriage_return; + caret = pp_point_sub(&caret, &carriage_return); carriage_return = initial_carriage_return; } else if (text[j] == ' ') { // Space - caret += space; - carriage_return += space; + caret = pp_point_add(&caret, &space); + carriage_return = pp_point_add(&carriage_return, &space); } else { - alright_fonts::render_character(text_metrics, text[j], pretty_poly::point_t(origin.x + caret.x, origin.y + caret.y), transform); + alright_fonts::render_character(text_metrics, text[j], caret, t); } - pretty_poly::point_t advance( - alright_fonts::measure_character(text_metrics, text[j]).w + text_metrics.letter_spacing, - 0 - ); - advance *= transform; - caret += advance; - carriage_return += advance; + pp_point_t advance = { + (PP_COORD_TYPE)alright_fonts::measure_character(text_metrics, text[j]).w + text_metrics.letter_spacing, + (PP_COORD_TYPE)0 + }; + advance = pp_point_transform(&advance, t); + caret = pp_point_add(&caret, &advance); + carriage_return = pp_point_add(&carriage_return, &advance); } i = next_break + 1; } - return Point(caret.x, caret.y); + return {caret.x, caret.y}; } } \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index 8502e5fb3..12322b31e 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -1,4 +1,4 @@ -#include "pretty_poly.hpp" +#include "pretty_poly/pretty-poly.h" #include "alright_fonts.hpp" #include "pico_graphics.hpp" @@ -9,52 +9,58 @@ namespace pimoroni { class PicoVector { private: - PicoGraphics *graphics; + static PicoGraphics *graphics; alright_fonts::text_metrics_t text_metrics; - const uint8_t alpha_map[4] {0, 128, 192, 255}; + static constexpr uint8_t alpha_map[4] {0, 128, 192, 255}; public: - PicoVector(PicoGraphics *graphics, void *mem = nullptr) : graphics(graphics) { - pretty_poly::init(mem); - - set_options([this](const pretty_poly::tile_t &tile) -> void { - uint8_t *tile_data = tile.data; - - if(this->graphics->supports_alpha_blend() && pretty_poly::settings::antialias != pretty_poly::NONE) { - if (this->graphics->render_pico_vector_tile({tile.bounds.x, tile.bounds.y, tile.bounds.w, tile.bounds.h}, - tile.data, - tile.stride, - (uint8_t)pretty_poly::settings::antialias)) { - return; - } - for(auto y = 0; y < tile.bounds.h; y++) { - for(auto x = 0; x < tile.bounds.w; x++) { - uint8_t alpha = *tile_data++; - if (alpha >= 4) { - this->graphics->set_pixel({x + tile.bounds.x, y + tile.bounds.y}); - } else if (alpha > 0) { - alpha = alpha_map[alpha]; - this->graphics->set_pixel_alpha({x + tile.bounds.x, y + tile.bounds.y}, alpha); - } + PicoVector(PicoGraphics *graphics, void *mem = nullptr) { + PicoVector::graphics = graphics; + + pp_tile_callback(PicoVector::tile_callback); + + 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); + } + + static void tile_callback(const pp_tile_t *tile) { + uint8_t *tile_data = tile->data; + + if(PicoVector::graphics->supports_alpha_blend() && _pp_antialias != PP_AA_NONE) { + if (PicoVector::graphics->render_pico_vector_tile({tile->x, tile->y, tile->w, tile->h}, + tile->data, + tile->stride, + (uint8_t)_pp_antialias)) { + return; + } + for(auto y = 0; y < tile->h; y++) { + for(auto x = 0; x < tile->w; x++) { + uint8_t alpha = *tile_data++; + if (alpha >= 4) { + PicoVector::graphics->set_pixel({x + tile->x, y + tile->y}); + } else if (alpha > 0) { + alpha = alpha_map[alpha]; + PicoVector::graphics->set_pixel_alpha({x + tile->x, y + tile->y}, alpha); } - tile_data += tile.stride - tile.bounds.w; } - } else { - for(auto y = 0; y < tile.bounds.h; y++) { - for(auto x = 0; x < tile.bounds.w; x++) { - uint8_t alpha = *tile_data++; - if (alpha) { - this->graphics->set_pixel({x + tile.bounds.x, y + tile.bounds.y}); - } + tile_data += tile->stride - tile->w; + } + } else { + for(auto y = 0; y < tile->h; y++) { + for(auto x = 0; x < tile->w; x++) { + uint8_t alpha = *tile_data++; + if (alpha) { + PicoVector::graphics->set_pixel({x + tile->x, y + tile->y}); } - tile_data += tile.stride - tile.bounds.w; } + tile_data += tile->stride - tile->w; } - }, graphics->supports_alpha_blend() ? pretty_poly::X4 : pretty_poly::NONE, {graphics->clip.x, graphics->clip.y, graphics->clip.w, graphics->clip.h}); + } } - void set_antialiasing(pretty_poly::antialias_t antialias) { - set_options(pretty_poly::settings::callback, antialias, pretty_poly::settings::clip); + void set_antialiasing(pp_antialias_t antialias) { + pp_antialias(antialias); } void set_font_size(unsigned int font_size) { @@ -69,19 +75,32 @@ namespace pimoroni { return result; } - void rotate(std::vector> &contours, Point origin, float angle); - void translate(std::vector> &contours, Point translation); + pp_point_t text(std::string_view text, pp_point_t origin, pp_mat3_t *t); + + void transform(pp_path_t *path, pp_mat3_t *t); + void transform(pp_poly_t *poly, pp_mat3_t *t); + + void rotate(pp_path_t *path, pp_point_t origin, float angle); + void rotate(pp_poly_t *poly, pp_point_t origin, float angle); - void rotate(pretty_poly::contour_t &contour, Point origin, float angle); - void translate(pretty_poly::contour_t &contour, Point translation); + void translate(pp_path_t *path, pp_point_t translation); + void translate(pp_poly_t *poly, pp_point_t translation); - Point text(std::string_view text, Point origin); - Point text(std::string_view text, Point origin, float angle); + void draw(pp_poly_t *poly); + void draw(pp_poly_t *poly, pp_mat3_t *t); - void polygon(std::vector> contours, Point origin = Point(0, 0), int scale=65536); + void draw(pp_path_t *path) { + pp_poly_t poly = {.paths = path, .count = 1}; + draw(&poly); + }; + + void draw(pp_path_t *path, pp_mat3_t *t) { + pp_poly_t poly = {.paths = path, .count = 1}; + draw(&poly, t); + }; static constexpr size_t pretty_poly_buffer_size() { - return pretty_poly::buffer_size(); + return 0; }; }; } \ No newline at end of file diff --git a/libraries/pico_vector/pretty_poly b/libraries/pico_vector/pretty_poly new file mode 160000 index 000000000..193967fcc --- /dev/null +++ b/libraries/pico_vector/pretty_poly @@ -0,0 +1 @@ +Subproject commit 193967fcc98d78f594f516bc7e4ddb08d7977ad7 diff --git a/libraries/pico_vector/pretty_poly.cpp b/libraries/pico_vector/pretty_poly.cpp deleted file mode 100644 index cc0c526d2..000000000 --- a/libraries/pico_vector/pretty_poly.cpp +++ /dev/null @@ -1,339 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "pretty_poly.hpp" - -#include "hardware/interp.h" - -#ifdef PP_DEBUG -#define debug(...) printf(__VA_ARGS__) -#else -#define debug(...) -#endif - -namespace pretty_poly { - - uint8_t *tile_buffer; - - int (*nodes)[32]; - unsigned *node_counts; - - // default tile bounds to X1 antialiasing - rect_t tile_bounds(0, 0, tile_buffer_size / node_buffer_size, node_buffer_size); - - // user settings - namespace settings { - rect_t clip(0, 0, 320, 240); - tile_callback_t callback; - antialias_t antialias = antialias_t::NONE; - } - - void init(void *memory) { - uintptr_t m = (uintptr_t)memory; - tile_buffer = new(memory) uint8_t[tile_buffer_size]; - node_counts = new((void *)(m + tile_buffer_size)) unsigned[node_buffer_size]; - nodes = new((void *)(m + tile_buffer_size + (node_buffer_size * sizeof(unsigned)))) int[node_buffer_size][32]; - } - - void set_options(tile_callback_t callback, antialias_t antialias, rect_t clip) { - settings::callback = callback; - settings::antialias = antialias; - settings::clip = clip; - - // recalculate the tile size for rendering based on antialiasing level - int tile_height = node_buffer_size >> antialias; - tile_bounds = rect_t(0, 0, tile_buffer_size / tile_height, tile_height); - } - - // dy step (returns 1, 0, or -1 if the supplied value is > 0, == 0, < 0) - inline constexpr int sign(int v) { - // assumes 32-bit int/unsigned - return ((unsigned)-v >> 31) - ((unsigned)v >> 31); - } - - // write out the tile bits - void debug_tile(const tile_t &tile) { - debug(" - tile %d, %d (%d x %d)\n", tile.bounds.x, tile.bounds.y, tile.bounds.w, tile.bounds.h); - for(auto y = 0; y < tile.bounds.h; y++) { - debug("[%3d]: ", y); - for(auto x = 0; x < tile.bounds.w; x++) { - debug("%d", tile.get_value(x, y)); - } - debug("\n"); - } - debug("-----------------------\n"); - } - - void add_line_segment_to_nodes(const point_t &start, const point_t &end) { - // 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) - int sx = start.x, sy = start.y, ex = end.x, ey = end.y; - - if(ey < sy) { - std::swap(sy, ey); - std::swap(sx, ex); - } - - // Early out if line is completely outside the tile, or has no lines - if (ey < 0 || sy >= (int)node_buffer_size || 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 = std::max(0, sy); - int count = std::min((int)node_buffer_size, ey) - y; - - // Handle cases where x is completely off to one side or other - if (std::max(sx, ex) <= 0) { - while (count--) { - nodes[y][node_counts[y]++] = 0; - ++y; - } - return; - } - - const int full_tile_width = (tile_bounds.w << settings::antialias); - if (std::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 = 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; - } - - 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; - } - } - - template - void build_nodes(const contour_t &contour, const tile_t &tile, point_t origin, int scale) { - int ox = (origin.x - tile.bounds.x) << settings::antialias; - int oy = (origin.y - tile.bounds.y) << settings::antialias; - - // start with the last point to close the loop - point_t last( - (((int(contour.points[contour.count - 1].x) * scale) << settings::antialias) / 65536) + ox, - (((int(contour.points[contour.count - 1].y) * scale) << settings::antialias) / 65536) + oy - ); - - for(auto i = 0u; i < contour.count; i++) { - point_t point( - (((int(contour.points[i].x) * scale) << settings::antialias) / 65536) + ox, - (((int(contour.points[i].y) * scale) << settings::antialias) / 65536) + oy - ); - - add_line_segment_to_nodes(last, point); - - last = point; - } - } - - void render_nodes(const tile_t &tile, rect_t &bounds) { - int maxy = -1; - bounds.y = 0; - bounds.x = tile.bounds.w; - int maxx = 0; - int anitialias_mask = (1 << settings::antialias) - 1; - - for(auto y = 0; y < (int)node_buffer_size; y++) { - if(node_counts[y] == 0) { - if (y == bounds.y) ++bounds.y; - continue; - } - - std::sort(&nodes[y][0], &nodes[y][0] + node_counts[y]); - - uint8_t* row_data = &tile.data[(y >> settings::antialias) * tile.stride]; - bool rendered_any = false; - for(auto i = 0u; 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 = std::max((ex - 1) >> settings::antialias, maxx); - - debug(" - render span at %d from %d to %d\n", y, sx, ex); - - if (settings::antialias) { - int ax = sx >> settings::antialias; - const int aex = ex >> settings::antialias; - - bounds.x = std::min(ax, bounds.x); - - if (ax == aex) { - row_data[ax] += ex - sx; - continue; - } - - row_data[ax] += (1 << settings::antialias) - (sx & anitialias_mask); - for(ax++; ax < aex; ax++) { - row_data[ax] += (1 << settings::antialias); - } - - // 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 { - bounds.x = std::min(sx, bounds.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 == bounds.y) { - debug(" - render nothing on line %d\n", y); - ++bounds.y; - } - } - - bounds.y >>= settings::antialias; - maxy >>= settings::antialias; - bounds.w = (maxx >= bounds.x) ? maxx + 1 - bounds.x : 0; - bounds.h = (maxy >= bounds.y) ? maxy + 1 - bounds.y : 0; - debug(" - rendered tile bounds %d, %d (%d x %d)\n", bounds.x, bounds.y, bounds.w, bounds.h); - } - - template - void draw_polygon(T *points, unsigned count) { - std::vector> contours; - contour_t c(points, count); - contours.push_back(c); - draw_polygon(contours); - } - - template - void draw_polygon(const std::vector>& contours, point_t origin, int scale) { - - debug("> draw polygon with %lu contours\n", contours.size()); - - if(contours.size() == 0) { - return; - } - - // determine extreme bounds - rect_t polygon_bounds = contours[0].bounds(); - for(auto &contour : contours) { - polygon_bounds = polygon_bounds.merge(contour.bounds()); - } - - polygon_bounds.x = ((polygon_bounds.x * scale) / 65536) + origin.x; - polygon_bounds.y = ((polygon_bounds.y * scale) / 65536) + origin.y; - polygon_bounds.w = ((polygon_bounds.w * scale) / 65536); - polygon_bounds.h = ((polygon_bounds.h * scale) / 65536); - - 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", settings::clip.x, settings::clip.y, settings::clip.w, settings::clip.h); - - 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; - - //memset(nodes, 0, node_buffer_size * sizeof(unsigned) * 32); - - // iterate over tiles - debug(" - processing tiles\n"); - for(auto y = polygon_bounds.y; y < polygon_bounds.y + polygon_bounds.h; y += tile_bounds.h) { - for(auto x = polygon_bounds.x; x < polygon_bounds.x + polygon_bounds.w; x += tile_bounds.w) { - tile_t tile; - tile.bounds = rect_t(x, y, tile_bounds.w, tile_bounds.h).intersection(settings::clip); - tile.stride = tile_bounds.w; - tile.data = tile_buffer; - debug(" : %d, %d (%d x %d)\n", tile.bounds.x, tile.bounds.y, tile.bounds.w, tile.bounds.h); - - // if no intersection then skip tile - if(tile.bounds.empty()) { - debug(" : empty when clipped, skipping\n"); - continue; - } - - // clear existing tile data and nodes - memset(node_counts, 0, node_buffer_size * sizeof(unsigned)); - memset(tile.data, 0, tile_buffer_size); - - // build the nodes for each contour - for(const contour_t &contour : contours) { - debug(" : build nodes for contour\n"); - build_nodes(contour, tile, origin, scale); - } - - debug(" : render the tile\n"); - // render the tile - rect_t bounds; - render_nodes(tile, bounds); - if (bounds.empty()) { - continue; - } - - tile.data += bounds.x + tile.stride * bounds.y; - tile.bounds.x += bounds.x; - tile.bounds.y += bounds.y; - tile.bounds.w = bounds.w; - tile.bounds.h = bounds.h; - - settings::callback(tile); - } - } - - interp_restore(interp1, &interp1_save); - } -} - -template void pretty_poly::draw_polygon(const std::vector>& contours, point_t origin, int scale); -template void pretty_poly::draw_polygon(const std::vector>& contours, point_t origin, int scale); -template void pretty_poly::draw_polygon(const std::vector>& contours, point_t origin, int scale); -template void pretty_poly::draw_polygon(const std::vector>& contours, point_t origin, int scale); \ No newline at end of file diff --git a/libraries/pico_vector/pretty_poly.hpp b/libraries/pico_vector/pretty_poly.hpp deleted file mode 100644 index ac31260a0..000000000 --- a/libraries/pico_vector/pretty_poly.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include - -#include "pretty_poly_types.hpp" - -namespace pretty_poly { - - class file_io { - private: - void *state; - size_t filesize = 0; - - public: - file_io(std::string_view path); - ~file_io(); - size_t seek(size_t pos); - size_t read(void *buf, size_t len); - size_t tell(); - bool fail(); - }; - - // buffer that each tile is rendered into before callback - constexpr unsigned tile_buffer_size = 1024; - - // polygon node buffer handles at most 16 line intersections per scanline - // is this enough for cjk/emoji? (requires a 2kB buffer) - constexpr unsigned node_buffer_size = 32; - - typedef std::function tile_callback_t; - - // user settings - namespace settings { - extern rect_t clip; - extern tile_callback_t callback; - extern antialias_t antialias; - } - - constexpr size_t buffer_size() { - return tile_buffer_size + (node_buffer_size * sizeof(unsigned)) + (node_buffer_size * 32 * sizeof(int)); - } - - constexpr size_t buffer_size(); - - void init(void *memory); - - void set_options(tile_callback_t callback, antialias_t antialias, rect_t clip); - - // dy step (returns 1, 0, or -1 if the supplied value is > 0, == 0, < 0) - inline constexpr int sign(int v); - - // write out the tile bits - void debug_tile(const tile_t &tile); - - void add_line_segment_to_nodes(const point_t &start, const point_t &end); - - template - void build_nodes(const contour_t &contour, const tile_t &tile, point_t origin = point_t(0, 0), int scale = 65536); - - void render_nodes(const tile_t &tile, rect_t &bounds); - - template - void draw_polygon(T *points, unsigned count); - - template - void draw_polygon(const std::vector>& contours, point_t origin = point_t(0, 0), int scale = 65536); -} \ No newline at end of file diff --git a/libraries/pico_vector/pretty_poly_types.hpp b/libraries/pico_vector/pretty_poly_types.hpp deleted file mode 100644 index 9cf400727..000000000 --- a/libraries/pico_vector/pretty_poly_types.hpp +++ /dev/null @@ -1,162 +0,0 @@ -#pragma once -#include -#include -#include - -#ifdef PP_DEBUG -#define debug(...) printf(__VA_ARGS__) -#else -#define debug(...) -#endif - -namespace pretty_poly { - - enum antialias_t {NONE = 0, X4 = 1, X16 = 2}; - - // 3x3 matrix for coordinate transformations - struct mat3_t { - float v00 = 0.0f, v10 = 0.0f, v20 = 0.0f, v01 = 0.0f, v11 = 0.0f, v21 = 0.0f, v02 = 0.0f, v12 = 0.0f, v22 = 0.0f; - mat3_t() = default; - mat3_t(const mat3_t &m) = default; - inline mat3_t& operator*= (const mat3_t &m) { - float r00 = this->v00 * m.v00 + this->v01 * m.v10 + this->v02 * m.v20; - float r01 = this->v00 * m.v01 + this->v01 * m.v11 + this->v02 * m.v21; - float r02 = this->v00 * m.v02 + this->v01 * m.v12 + this->v02 * m.v22; - float r10 = this->v10 * m.v00 + this->v11 * m.v10 + this->v12 * m.v20; - float r11 = this->v10 * m.v01 + this->v11 * m.v11 + this->v12 * m.v21; - float r12 = this->v10 * m.v02 + this->v11 * m.v12 + this->v12 * m.v22; - float r20 = this->v20 * m.v00 + this->v21 * m.v10 + this->v22 * m.v20; - float r21 = this->v20 * m.v01 + this->v21 * m.v11 + this->v22 * m.v21; - float r22 = this->v20 * m.v02 + this->v21 * m.v12 + this->v22 * m.v22; - this->v00 = r00; this->v01 = r01; this->v02 = r02; - this->v10 = r10; this->v11 = r11; this->v12 = r12; - this->v20 = r20; this->v21 = r21; this->v22 = r22; - return *this; - } - - static mat3_t identity() {mat3_t m; m.v00 = m.v11 = m.v22 = 1.0f; return m;} - static mat3_t rotation(float a) { - float c = cosf(a), s = sinf(a); mat3_t r = mat3_t::identity(); - r.v00 = c; r.v01 = -s; r.v10 = s; r.v11 = c; return r;} - static mat3_t translation(float x, float y) { - mat3_t r = mat3_t::identity(); r.v02 = x; r.v12 = y; return r;} - static mat3_t scale(float x, float y) { - mat3_t r = mat3_t::identity(); r.v00 = x; r.v11 = y; return r;} - }; - - // 2x2 matrix for rotations and scales - struct mat2_t { - float v00 = 0.0f, v10 = 0.0f, v01 = 0.0f, v11 = 0.0f; - mat2_t() = default; - mat2_t(const mat2_t &m) = default; - inline mat2_t& operator*= (const mat2_t &m) { - float r00 = this->v00 * m.v00 + this->v01 * m.v10; - float r01 = this->v00 * m.v01 + this->v01 * m.v11; - float r10 = this->v10 * m.v00 + this->v11 * m.v10; - float r11 = this->v10 * m.v01 + this->v11 * m.v11; - this->v00 = r00; this->v01 = r01; - this->v10 = r10; this->v11 = r11; - return *this; - } - - static mat2_t identity() {mat2_t m; m.v00 = m.v11 = 1.0f; return m;} - static mat2_t rotation(float a) { - float c = cosf(a), s = sinf(a); mat2_t r; - r.v00 = c; r.v01 = -s; r.v10 = s; r.v11 = c; return r;} - static mat2_t scale(float x, float y) { - mat2_t r; r.v00 = x; r.v11 = y; return r;} - }; - - // point type for contour points - template - struct __attribute__ ((packed)) point_t { - T x, y; - point_t(T x, T y) : x(x), y(y) {} - point_t() : x(0), y(0) {} - inline point_t& operator-= (const point_t &a) {x -= a.x; y -= a.y; return *this;} - inline point_t& operator+= (const point_t &a) {x += a.x; y += a.y; return *this;} - inline point_t& operator*= (const float a) {x *= a; y *= a; return *this;} - inline point_t& operator*= (const mat2_t &a) {this->transform(a); return *this;} - inline point_t& operator*= (const mat3_t &a) {this->transform(a); return *this;} - inline point_t& operator/= (const float a) {x /= a; y /= a; return *this;} - inline point_t& operator/= (const point_t &a) {x /= a.x; y /= a.y; return *this;} - void transform(const mat3_t &m) { - float tx = x, ty = y; - this->x = (m.v00 * tx + m.v01 * ty + m.v02); - this->y = (m.v10 * tx + m.v11 * ty + m.v12); - } - void transform(const mat2_t &m) { - float tx = x, ty = y; - this->x = (m.v00 * tx + m.v01 * ty); - this->y = (m.v10 * tx + m.v11 * ty); - } - - }; - - template inline point_t operator- (point_t lhs, const point_t &rhs) { lhs -= rhs; return lhs; } - template inline point_t operator- (const point_t &rhs) { return point_t(-rhs.x, -rhs.y); } - template inline point_t operator+ (point_t lhs, const point_t &rhs) { lhs += rhs; return lhs; } - template inline point_t operator* (point_t lhs, const float rhs) { lhs *= rhs; return lhs; } - template inline point_t operator* (point_t lhs, const point_t &rhs) { lhs *= rhs; return lhs; } - template inline point_t operator* (point_t lhs, const mat3_t &rhs) { lhs *= rhs; return lhs; } - template inline point_t operator/ (point_t lhs, const float rhs) { lhs /= rhs; return lhs; } - template inline point_t operator/ (point_t lhs, const point_t &rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } - - - // rect type for bounds and clipping rectangles - struct rect_t { - int x, y, w, h; - rect_t() : x(0), y(0), w(0), h(0) {} - rect_t(int x, int y, int w, int h) : x(x), y(y), w(w), h(h) {} - bool empty() const {return this->w == 0 || this->h == 0;} - rect_t intersection(const rect_t &c) { - return rect_t(std::max(this->x, c.x), std::max(this->y, c.y), - std::max(0, std::min(this->x + this->w, c.x + c.w) - std::max(this->x, c.x)), - std::max(0, std::min(this->y + this->h, c.y + c.h) - std::max(this->y, c.y))); - } - rect_t merge(const rect_t &c) { - return rect_t(std::min(this->x, c.x), std::min(this->y, c.y), - std::max(this->x + this->w, c.x + c.w) - std::min(this->x, c.x), - std::max(this->y + this->h, c.y + c.h) - std::min(this->y, c.y)); - } - }; - - struct tile_t { - rect_t bounds; - unsigned stride; - uint8_t *data; - - tile_t() {}; - - inline int get_value(int x, int y) const { - return this->data[x + y * this->stride]; - } - }; - - template - struct contour_t { - point_t *points; - unsigned count; - - contour_t() {} - contour_t(const std::vector>& v) : points(v.data()), count(v.size()) {}; - contour_t(point_t *points, unsigned count) : points(points), count(count) {}; - - // TODO: Make this work, it's so much nicer to use auto point : contour - //point_t *begin() const { return points; }; - //point_t *end() const { return points + count * sizeof(point_t); }; - - rect_t bounds() const { - T minx = this->points[0].x, maxx = minx; - T miny = this->points[0].y, maxy = miny; - for(auto i = 1u; i < this->count; i++) { - minx = std::min(minx, this->points[i].x); - miny = std::min(miny, this->points[i].y); - maxx = std::max(maxx, this->points[i].x); - maxy = std::max(maxy, this->points[i].y); - } - return rect_t(minx, miny, maxx - minx, maxy - miny); - } - }; - -} From 6f2d4bfff548b8384a2fc7013fdfe9d5dab0d834 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 29 Sep 2023 09:04:14 +0100 Subject: [PATCH 04/35] PicoVector: Rewrite MicroPython bindings. --- .../pico_w_explorer_vector.cpp | 12 +- .../modules/picovector/micropython.cmake | 1 - micropython/modules/picovector/picovector.c | 4 +- micropython/modules/picovector/picovector.cpp | 159 ++++++++++-------- micropython/modules/picovector/picovector.h | 2 +- 5 files changed, 103 insertions(+), 75 deletions(-) diff --git a/examples/pico_w_explorer/pico_w_explorer_vector.cpp b/examples/pico_w_explorer/pico_w_explorer_vector.cpp index af8e3ec50..20db5732e 100644 --- a/examples/pico_w_explorer/pico_w_explorer_vector.cpp +++ b/examples/pico_w_explorer/pico_w_explorer_vector.cpp @@ -20,6 +20,8 @@ int main() { Pen WHITE = graphics.create_pen(255, 255, 255); Pen BLACK = graphics.create_pen(0, 0, 0); + float angle = 0.0f; + while(true) { graphics.set_pen(BLACK); graphics.clear(); @@ -35,16 +37,18 @@ int main() { }; pp_poly_t poly = {.paths = paths, .count = 2}; - vector.rotate(&poly, {0, 0}, 45.0f); - vector.translate(&poly, {128, 128}); + vector.rotate(&poly, {0, 0}, angle); + vector.translate(&poly, {160, 120}); vector.draw(&poly); - pp_mat3_t t = pp_mat3_identity(); - vector.text("Hello World", {0, 0}, &t); + //pp_mat3_t t = pp_mat3_identity(); + //vector.text("Hello World", {0, 0}, &t); // update screen st7789.update(&graphics); + + angle += 1.0f; } return 0; diff --git a/micropython/modules/picovector/micropython.cmake b/micropython/modules/picovector/micropython.cmake index c513cdcc5..afb3a3936 100644 --- a/micropython/modules/picovector/micropython.cmake +++ b/micropython/modules/picovector/micropython.cmake @@ -2,7 +2,6 @@ add_library(usermod_picovector INTERFACE) target_sources(usermod_picovector INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_vector/pico_vector.cpp - ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_vector/pretty_poly.cpp ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_vector/alright_fonts.cpp ${CMAKE_CURRENT_LIST_DIR}/picovector.c ${CMAKE_CURRENT_LIST_DIR}/picovector.cpp diff --git a/micropython/modules/picovector/picovector.c b/micropython/modules/picovector/picovector.c index a67a7072b..f724447f0 100644 --- a/micropython/modules/picovector/picovector.c +++ b/micropython/modules/picovector/picovector.c @@ -22,7 +22,7 @@ MP_DEFINE_CONST_OBJ_TYPE( MP_TYPE_FLAG_NONE, make_new, POLYGON_make_new, print, POLYGON_print, - iter, POLYGON_getiter, + iter, PATH_getiter, locals_dict, (mp_obj_dict_t*)&POLYGON_locals_dict ); MP_DEFINE_CONST_OBJ_TYPE( @@ -45,7 +45,7 @@ const mp_obj_type_t POLYGON_type = { .name = MP_QSTR_polygon, .make_new = POLYGON_make_new, .print = POLYGON_print, - .iter = POLYGON_getiter, + .iter = PATH_getiter, .locals_dict = (mp_obj_dict_t*)&POLYGON_locals_dict, }; const mp_obj_type_t REGULAR_POLYGON_type = { diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index a77a38b2c..5e837c3fd 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -24,12 +24,12 @@ typedef struct _VECTOR_obj_t { PicoVector *vector; } _VECTOR_obj_t; -typedef struct _POLYGON_obj_t { +typedef struct _PATH_obj_t { mp_obj_base_t base; - pretty_poly::contour_t contour; -} _POLYGON_obj_t; + pp_path_t path; +} _PATH_obj_t; -pretty_poly::file_io::file_io(std::string_view filename) { +file_io::file_io(std::string_view filename) { mp_obj_t fn = mp_obj_new_str(filename.data(), (mp_uint_t)filename.size()); //mp_printf(&mp_plat_print, "Opening file %s\n", filename.data()); @@ -50,18 +50,18 @@ pretty_poly::file_io::file_io(std::string_view filename) { this->state = (void *)fhandle; } -pretty_poly::file_io::~file_io() { +file_io::~file_io() { mp_stream_close((mp_obj_t)this->state); } -size_t pretty_poly::file_io::read(void *buf, size_t len) { +size_t file_io::read(void *buf, size_t len) { //mp_printf(&mp_plat_print, "Reading %lu bytes\n", len); mp_obj_t fhandle = this->state; int error; return mp_stream_read_exactly(fhandle, buf, len, &error); } -size_t pretty_poly::file_io::tell() { +size_t file_io::tell() { mp_obj_t fhandle = this->state; struct mp_stream_seek_t seek_s; seek_s.offset = 0; @@ -78,12 +78,12 @@ size_t pretty_poly::file_io::tell() { return seek_s.offset; } -bool pretty_poly::file_io::fail() { +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 pretty_poly::file_io::seek(size_t pos) { +// 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; struct mp_stream_seek_t seek_s; seek_s.offset = pos; @@ -100,6 +100,7 @@ size_t pretty_poly::file_io::seek(size_t pos) { return seek_s.offset; } +/* static const std::string_view mp_obj_to_string_r(const mp_obj_t &obj) { if(mp_obj_is_str_or_bytes(obj)) { GET_STR_DATA_LEN(obj, str, str_len); @@ -107,6 +108,7 @@ static const std::string_view mp_obj_to_string_r(const mp_obj_t &obj) { } mp_raise_TypeError("can't convert object to str implicitly"); } +*/ /* POLYGON */ @@ -122,20 +124,20 @@ mp_obj_t RECTANGLE_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_k mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - _POLYGON_obj_t *self = mp_obj_malloc_with_finaliser(_POLYGON_obj_t, &POLYGON_type); + _PATH_obj_t *self = mp_obj_malloc_with_finaliser(_PATH_obj_t, &POLYGON_type); int x = args[ARG_x].u_int; int y = args[ARG_y].u_int; int w = args[ARG_w].u_int; int h = args[ARG_h].u_int; - self->contour.points = m_new(pretty_poly::point_t, 4); - self->contour.count = 4; + self->path.points = m_new(pp_point_t, 4); + self->path.count = 4; - self->contour.points[0] = {picovector_point_type(x), picovector_point_type(y)}; - self->contour.points[1] = {picovector_point_type(x + w), picovector_point_type(y)}; - self->contour.points[2] = {picovector_point_type(x + w), picovector_point_type(y + h)}; - self->contour.points[3] = {picovector_point_type(x), picovector_point_type(y + h)}; + self->path.points[0] = {picovector_point_type(x), picovector_point_type(y)}; + self->path.points[1] = {picovector_point_type(x + w), picovector_point_type(y)}; + self->path.points[2] = {picovector_point_type(x + w), picovector_point_type(y + h)}; + self->path.points[3] = {picovector_point_type(x), picovector_point_type(y + h)}; return self; } @@ -153,7 +155,7 @@ mp_obj_t REGULAR_POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - _POLYGON_obj_t *self = mp_obj_malloc_with_finaliser(_POLYGON_obj_t, &POLYGON_type); + _PATH_obj_t *self = mp_obj_malloc_with_finaliser(_PATH_obj_t, &POLYGON_type); Point origin(args[ARG_x].u_int, args[ARG_y].u_int); unsigned int sides = args[ARG_sides].u_int; @@ -168,12 +170,12 @@ mp_obj_t REGULAR_POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size float angle = (360.0f / sides) * (M_PI / 180.0f); - self->contour.points = m_new(pretty_poly::point_t, sides); - self->contour.count = sides; + self->path.points = m_new(pp_point_t, sides); + self->path.count = sides; for(auto s = 0u; s < sides; s++) { float current_angle = angle * s + rotation; - self->contour.points[s] = { + self->path.points[s] = { (picovector_point_type)(cos(current_angle) * radius) + o_x, (picovector_point_type)(sin(current_angle) * radius) + o_y }; @@ -183,15 +185,15 @@ mp_obj_t REGULAR_POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size } mp_obj_t POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - _POLYGON_obj_t *self = mp_obj_malloc_with_finaliser(_POLYGON_obj_t, &POLYGON_type); + _PATH_obj_t *self = mp_obj_malloc_with_finaliser(_PATH_obj_t, &POLYGON_type); size_t num_points = n_args; const mp_obj_t *points = all_args; if(num_points < 3) mp_raise_ValueError("Polygon: At least 3 points required."); - self->contour.points = m_new(pretty_poly::point_t, num_points); - self->contour.count = num_points; + self->path.points = m_new(pp_point_t, num_points); + self->path.count = num_points; for(auto i = 0u; i < num_points; i++) { mp_obj_t c_obj = points[i]; @@ -202,7 +204,7 @@ mp_obj_t POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, if(t_point->len != 2) mp_raise_ValueError("Tuple must have X, Y"); - self->contour.points[i] = { + self->path.points[i] = { (picovector_point_type)mp_obj_get_int(t_point->items[0]), (picovector_point_type)mp_obj_get_int(t_point->items[1]), }; @@ -212,54 +214,61 @@ mp_obj_t POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, } mp_obj_t POLYGON_centroid(mp_obj_t self_in) { - _POLYGON_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLYGON_obj_t); + _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); - pretty_poly::point_t sum(0, 0); + PP_COORD_TYPE sum_x = (PP_COORD_TYPE)0; + PP_COORD_TYPE sum_y = (PP_COORD_TYPE)0; - for(auto i = 0u; i < self->contour.count; i++) { - sum += self->contour.points[i]; + for(auto i = 0u; i < self->path.count; i++) { + sum_x += self->path.points[i].x; + sum_y += self->path.points[i].y; } - sum /= (float)self->contour.count; + sum_x /= (float)self->path.count; + sum_y /= (float)self->path.count; mp_obj_t tuple[2]; - tuple[0] = mp_obj_new_int((int)(sum.x)); - tuple[1] = mp_obj_new_int((int)(sum.y)); + tuple[0] = mp_obj_new_int((int)(sum_x)); + tuple[1] = mp_obj_new_int((int)(sum_y)); return mp_obj_new_tuple(2, tuple); } mp_obj_t POLYGON_bounds(mp_obj_t self_in) { - _POLYGON_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLYGON_obj_t); + _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); + + pp_rect_t bounds = pp_contour_bounds(&self->path); mp_obj_t tuple[4]; - tuple[0] = mp_obj_new_int((int)(self->contour.bounds().x)); - tuple[1] = mp_obj_new_int((int)(self->contour.bounds().y)); - tuple[2] = mp_obj_new_int((int)(self->contour.bounds().w)); - tuple[3] = mp_obj_new_int((int)(self->contour.bounds().h)); + tuple[0] = mp_obj_new_int((int)(bounds.x)); + tuple[1] = mp_obj_new_int((int)(bounds.y)); + tuple[2] = mp_obj_new_int((int)(bounds.w)); + tuple[3] = mp_obj_new_int((int)(bounds.h)); return mp_obj_new_tuple(4, tuple); } void POLYGON_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; - _POLYGON_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLYGON_obj_t); + _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); + + pp_rect_t bounds = pp_contour_bounds(&self->path); mp_print_str(print, "Polygon(points = "); - mp_obj_print_helper(print, mp_obj_new_int(self->contour.count), PRINT_REPR); + mp_obj_print_helper(print, mp_obj_new_int(self->path.count), PRINT_REPR); mp_print_str(print, ", bounds = "); - mp_obj_print_helper(print, mp_obj_new_int(self->contour.bounds().x), PRINT_REPR); + mp_obj_print_helper(print, mp_obj_new_int(bounds.x), PRINT_REPR); mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_obj_new_int(self->contour.bounds().y), PRINT_REPR); + mp_obj_print_helper(print, mp_obj_new_int(bounds.y), PRINT_REPR); mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_obj_new_int(self->contour.bounds().w), PRINT_REPR); + mp_obj_print_helper(print, mp_obj_new_int(bounds.w), PRINT_REPR); mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_obj_new_int(self->contour.bounds().h), PRINT_REPR); + mp_obj_print_helper(print, mp_obj_new_int(bounds.h), PRINT_REPR); mp_print_str(print, ")"); } mp_obj_t POLYGON__del__(mp_obj_t self_in) { - _POLYGON_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLYGON_obj_t); + _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); (void)self; // TODO: Do we actually need to free anything here, if it's on GC heap it should get collected return mp_const_none; @@ -272,26 +281,26 @@ typedef struct _mp_obj_polygon_it_t { size_t cur; } mp_obj_polygon_it_t; -static mp_obj_t py_image_it_iternext(mp_obj_t self_in) { +static mp_obj_t py_path_it_iternext(mp_obj_t self_in) { mp_obj_polygon_it_t *self = MP_OBJ_TO_PTR2(self_in, mp_obj_polygon_it_t); - _POLYGON_obj_t *polygon = MP_OBJ_TO_PTR2(self->polygon, _POLYGON_obj_t); + _PATH_obj_t *path = MP_OBJ_TO_PTR2(self->polygon, _PATH_obj_t); //mp_printf(&mp_plat_print, "points: %d, current: %d\n", polygon->contour.count, self->cur); - if(self->cur >= polygon->contour.count) return MP_OBJ_STOP_ITERATION; + if(self->cur >= path->path.count) return MP_OBJ_STOP_ITERATION; mp_obj_t tuple[2]; - tuple[0] = mp_obj_new_int((int)(polygon->contour.points[self->cur].x)); - tuple[1] = mp_obj_new_int((int)(polygon->contour.points[self->cur].y)); + tuple[0] = mp_obj_new_int((int)(path->path.points[self->cur].x)); + tuple[1] = mp_obj_new_int((int)(path->path.points[self->cur].y)); self->cur++; return mp_obj_new_tuple(2, tuple); } -mp_obj_t POLYGON_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { +mp_obj_t PATH_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { mp_obj_polygon_it_t *o = (mp_obj_polygon_it_t *)iter_buf; o->base.type = &mp_type_polymorph_iter; - o->iternext = py_image_it_iternext; + o->iternext = py_path_it_iternext; o->polygon = o_in; o->cur = 0; return MP_OBJ_FROM_PTR(o); @@ -317,7 +326,8 @@ mp_obj_t VECTOR_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, // The PicoVector class calls `pretty_poly::init()` with the memory region // it does not store a pointer to this, so we need to store one ourselves - self->mem = m_new(uint8_t, PicoVector::pretty_poly_buffer_size()); + // TODO: C Pretty Poly does not support runtime memory allocation + //self->mem = m_new(uint8_t, PicoVector::pretty_poly_buffer_size()); self->vector = m_new_class(PicoVector, graphics->graphics, self->mem); @@ -326,12 +336,15 @@ mp_obj_t VECTOR_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size) { _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); + (void)self; int font_size = mp_obj_get_int(size); + (void)font_size; bool result = false; if (mp_obj_is_str(font)) { - result = self->vector->set_font(mp_obj_to_string_r(font), font_size); + // TODO: Implement when Alright Fonts rewrite is ready + //result = self->vector->set_font(mp_obj_to_string_r(font), font_size); } else { @@ -341,16 +354,19 @@ mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size) { mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size) { _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); + (void)self; int font_size = mp_obj_get_int(size); - self->vector->set_font_size(font_size); + (void)font_size; + // TODO: Implement when Alright Fonts rewrite is ready + //self->vector->set_font_size(font_size); return mp_const_none; } mp_obj_t VECTOR_set_antialiasing(mp_obj_t self_in, mp_obj_t aa) { _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); - self->vector->set_antialiasing((pretty_poly::antialias_t)mp_obj_get_int(aa)); + self->vector->set_antialiasing((pp_antialias_t)mp_obj_get_int(aa)); return mp_const_none; } @@ -368,6 +384,7 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _VECTOR_obj_t); + (void)self; mp_obj_t text_obj = args[ARG_text].u_obj; @@ -379,11 +396,14 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) 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) { - self->vector->text(t, Point(x, y)); + // 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)); + //self->vector->text(t, Point(x, y), mp_obj_get_float(args[ARG_angle].u_obj)); } return mp_const_none; @@ -406,13 +426,13 @@ mp_obj_t VECTOR_rotate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_arg if(!MP_OBJ_IS_TYPE(args[ARG_polygon].u_obj, &POLYGON_type)) mp_raise_TypeError("rotate: polygon required"); - _POLYGON_obj_t *poly = MP_OBJ_TO_PTR2(args[ARG_polygon].u_obj, _POLYGON_obj_t); + _PATH_obj_t *poly = MP_OBJ_TO_PTR2(args[ARG_polygon].u_obj, _PATH_obj_t); - Point origin = Point(args[ARG_origin_x].u_int, args[ARG_origin_y].u_int); + pp_point_t origin = {(PP_COORD_TYPE)args[ARG_origin_x].u_int, (PP_COORD_TYPE)args[ARG_origin_y].u_int}; float angle = mp_obj_get_float(args[ARG_angle].u_obj); - self->vector->rotate(poly->contour, origin, angle); + self->vector->rotate(&poly->path, origin, angle); return mp_const_none; } @@ -433,11 +453,11 @@ mp_obj_t VECTOR_translate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ if(!MP_OBJ_IS_TYPE(args[ARG_polygon].u_obj, &POLYGON_type)) mp_raise_TypeError("rotate: polygon required"); - _POLYGON_obj_t *poly = MP_OBJ_TO_PTR2(args[ARG_polygon].u_obj, _POLYGON_obj_t); + _PATH_obj_t *poly = MP_OBJ_TO_PTR2(args[ARG_polygon].u_obj, _PATH_obj_t); - Point translate = Point(args[ARG_x].u_int, args[ARG_y].u_int); + pp_point_t translate = {(PP_COORD_TYPE)args[ARG_x].u_int, (PP_COORD_TYPE)args[ARG_y].u_int}; - self->vector->translate(poly->contour, translate); + self->vector->translate(&poly->path, translate); return mp_const_none; } @@ -449,18 +469,23 @@ mp_obj_t VECTOR_draw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(pos_args[0], _VECTOR_obj_t); - std::vector> contours; + pp_poly_t group; + group.count = num_polygons; + group.paths = (pp_path_t *)malloc(sizeof(pp_path_t) * num_polygons); for(auto i = 0u; i < num_polygons; i++) { mp_obj_t poly_obj = polygons[i]; if(!MP_OBJ_IS_TYPE(poly_obj, &POLYGON_type)) mp_raise_TypeError("draw: Polygon required."); - _POLYGON_obj_t *poly = MP_OBJ_TO_PTR2(poly_obj, _POLYGON_obj_t); - contours.emplace_back(poly->contour.points, poly->contour.count); + _PATH_obj_t *poly = MP_OBJ_TO_PTR2(poly_obj, _PATH_obj_t); + group.paths[i].points = poly->path.points; + group.paths[i].count = poly->path.count; } - self->vector->polygon(contours); + self->vector->draw(&group); + + free(group.paths); return mp_const_none; } diff --git a/micropython/modules/picovector/picovector.h b/micropython/modules/picovector/picovector.h index 899f1ae47..547c59a1e 100644 --- a/micropython/modules/picovector/picovector.h +++ b/micropython/modules/picovector/picovector.h @@ -12,7 +12,7 @@ extern mp_obj_t RECTANGLE_make_new(const mp_obj_type_t *type, size_t n_args, siz extern void POLYGON_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind); extern mp_obj_t POLYGON_centroid(mp_obj_t self_in); extern mp_obj_t POLYGON_bounds(mp_obj_t self_in); -extern mp_obj_t POLYGON_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf); +extern mp_obj_t PATH_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf); extern mp_obj_t POLYGON__del__(mp_obj_t self_in); From da262acec090c3c57c4ca9c4d39df69e916d2876 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 17 Apr 2024 16:17:54 +0100 Subject: [PATCH 05/35] PicoVector: Suppress errors. Ignore sign compare and narrowing conversion errors in pretty-poly.h. --- libraries/pico_vector/pico_vector.cmake | 8 +++++++- micropython/modules/picovector/micropython.cmake | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/libraries/pico_vector/pico_vector.cmake b/libraries/pico_vector/pico_vector.cmake index c9056440b..73f17c5f1 100644 --- a/libraries/pico_vector/pico_vector.cmake +++ b/libraries/pico_vector/pico_vector.cmake @@ -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) \ No newline at end of file +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" +) \ No newline at end of file diff --git a/micropython/modules/picovector/micropython.cmake b/micropython/modules/picovector/micropython.cmake index afb3a3936..381d8b50c 100644 --- a/micropython/modules/picovector/micropython.cmake +++ b/micropython/modules/picovector/micropython.cmake @@ -20,3 +20,9 @@ target_compile_definitions(usermod_picovector INTERFACE target_link_libraries(usermod_picovector INTERFACE hardware_interp) 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-narrowing" +) From 0354b8bd7aaf4ceb1a214a424223b264d0edfe65 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 18 Apr 2024 10:40:33 +0100 Subject: [PATCH 06/35] PicoVector: Vendor pretty-poly and tweak rotation. --- libraries/pico_vector/alright_fonts.hpp | 2 +- libraries/pico_vector/pico_vector.hpp | 4 +- libraries/pico_vector/pretty-poly.h | 619 ++++++++++++++++++++++++ libraries/pico_vector/pretty_poly | 1 - 4 files changed, 623 insertions(+), 3 deletions(-) create mode 100644 libraries/pico_vector/pretty-poly.h delete mode 160000 libraries/pico_vector/pretty_poly diff --git a/libraries/pico_vector/alright_fonts.hpp b/libraries/pico_vector/alright_fonts.hpp index 65996f2b8..2bd83b65e 100644 --- a/libraries/pico_vector/alright_fonts.hpp +++ b/libraries/pico_vector/alright_fonts.hpp @@ -6,7 +6,7 @@ #include #include -#include "pretty_poly/pretty-poly.h" +#include "pretty-poly.h" #include "file_io.hpp" namespace alright_fonts { diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index 12322b31e..86fc2671d 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -1,7 +1,9 @@ -#include "pretty_poly/pretty-poly.h" +#include "pretty-poly.h" #include "alright_fonts.hpp" #include "pico_graphics.hpp" +pp_rect_t pp_contour_bounds(const pp_path_t *c); + namespace pimoroni { // Integer point types cause compound error in transformations diff --git a/libraries/pico_vector/pretty-poly.h b/libraries/pico_vector/pretty-poly.h new file mode 100644 index 000000000..0bd554556 --- /dev/null +++ b/libraries/pico_vector/pretty-poly.h @@ -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 +#include +#include +#include +#include + +#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 \ No newline at end of file diff --git a/libraries/pico_vector/pretty_poly b/libraries/pico_vector/pretty_poly deleted file mode 160000 index 193967fcc..000000000 --- a/libraries/pico_vector/pretty_poly +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 193967fcc98d78f594f516bc7e4ddb08d7977ad7 From 27fd61b5c0438a24998f046d4d3103465d4b566a Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 18 Apr 2024 10:57:37 +0100 Subject: [PATCH 07/35] PicoVector: Swap rotate translation order. --- libraries/pico_vector/pico_vector.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp index 4a609f32b..25d533b9f 100644 --- a/libraries/pico_vector/pico_vector.cpp +++ b/libraries/pico_vector/pico_vector.cpp @@ -17,9 +17,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 +37,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); } From 1fe1d3ce30a0a4a258969b26b284adb41e5c98a1 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 18 Apr 2024 12:37:33 +0100 Subject: [PATCH 08/35] PicoVector: alright-fonts bringup. --- libraries/pico_vector/af-file-io.h | 16 + libraries/pico_vector/af-memory.h | 5 + libraries/pico_vector/alright-fonts.h | 361 ++++++++++++++++++ libraries/pico_vector/pico_vector.cpp | 22 +- libraries/pico_vector/pico_vector.hpp | 29 +- micropython/modules/picovector/picovector.cpp | 81 ++-- 6 files changed, 471 insertions(+), 43 deletions(-) create mode 100644 libraries/pico_vector/af-file-io.h create mode 100644 libraries/pico_vector/af-memory.h create mode 100644 libraries/pico_vector/alright-fonts.h diff --git a/libraries/pico_vector/af-file-io.h b/libraries/pico_vector/af-file-io.h new file mode 100644 index 000000000..eb5a413b6 --- /dev/null +++ b/libraries/pico_vector/af-file-io.h @@ -0,0 +1,16 @@ +#include +#include + +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); +} \ No newline at end of file diff --git a/libraries/pico_vector/af-memory.h b/libraries/pico_vector/af-memory.h new file mode 100644 index 000000000..81c647bda --- /dev/null +++ b/libraries/pico_vector/af-memory.h @@ -0,0 +1,5 @@ +extern "C" { + void *af_malloc(size_t size); + void *af_realloc(void *p, size_t size); + void af_free(void *p); +} \ No newline at end of file diff --git a/libraries/pico_vector/alright-fonts.h b/libraries/pico_vector/alright-fonts.h new file mode 100644 index 000000000..701c7a078 --- /dev/null +++ b/libraries/pico_vector/alright-fonts.h @@ -0,0 +1,361 @@ +/* + + 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 +#include +#include +#include +#include +#include + +#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 + +#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, wchar_t codepoint, af_text_metrics_t *tm); +void af_render(af_face_t *face, wchar_t *text, af_text_metrics_t *tm); +pp_rect_t af_measure(af_face_t *face, const wchar_t *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); + 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, wchar_t 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]; + free(path->points); + } + free(poly.paths); +} + +void af_render_character(af_face_t *face, wchar_t 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, wchar_t *text, af_text_metrics_t *tm) { + int line_width = 0; + wchar_t *end = wcschr(text, L'\n'); + for(wchar_t 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, wchar_t *text, af_text_metrics_t *tm) { + int max_width = 0; + + wchar_t *end = wcschr(text, L'\n'); + while(end) { + int width = get_line_width(face, text, tm); + max_width = max_width < width ? width : max_width; + text = end + 1; + end = wcschr(text, L'\n'); + } + + return max_width; +} + + +void af_render(af_face_t *face, wchar_t *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; + + wchar_t *end = wcschr(text, L'\n'); + while(end) { + int line_width = get_line_width(face, text, tm); + + for(wchar_t 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 = wcschr(text, L'\n'); + + caret.x = 0; + caret.y += line_height; + } + + + + pp_transform(old); +} + +pp_rect_t af_measure(af_face_t *face, const wchar_t *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 < wcslen(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 \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp index 25d533b9f..8566e43fb 100644 --- a/libraries/pico_vector/pico_vector.cpp +++ b/libraries/pico_vector/pico_vector.cpp @@ -1,4 +1,5 @@ #define PP_IMPLEMENTATION +#define AF_IMPLEMENTATION #include "pico_vector.hpp" #include @@ -55,7 +56,7 @@ 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::wstring_view text, pp_point_t offset, pp_mat3_t *t) { pp_point_t caret = {0, 0}; // Align text from the bottom left @@ -68,7 +69,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; + wchar_t spc = L' '; + + space.x = af_measure(text_metrics.face, &spc, &text_metrics).w; if (space.x == 0) { space.x = text_metrics.word_spacing; } @@ -97,7 +100,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; } @@ -107,17 +110,22 @@ namespace pimoroni { } for(size_t j = i; j < std::min(next_break + 1, text.length()); j++) { - if (text[j] == '\n') { // Linebreak + if (text[j] == L'\n') { // Linebreak caret = pp_point_sub(&caret, &carriage_return); carriage_return = initial_carriage_return; - } else if (text[j] == ' ') { // Space + } else if (text[j] == L' ') { // Space 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, (const wchar_t *)text[j], &text_metrics).w + text_metrics.letter_spacing, (PP_COORD_TYPE)0 }; advance = pp_point_transform(&advance, t); diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index 86fc2671d..fd1532b0f 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -1,5 +1,21 @@ + +#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) + #include "pretty-poly.h" -#include "alright_fonts.hpp" +#include "alright-fonts.h" #include "pico_graphics.hpp" pp_rect_t pp_contour_bounds(const pp_path_t *c); @@ -12,7 +28,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: @@ -66,18 +82,21 @@ 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); + //bool result = text_metrics.face.load(font_path); + void* font = fileio_open(font_path.data()); + af_load_font_file(font, text_metrics.face); + bool result = false; 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::wstring_view text, pp_point_t origin, pp_mat3_t *t); void transform(pp_path_t *path, pp_mat3_t *t); void transform(pp_poly_t *poly, pp_mat3_t *t); diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index 5e837c3fd..d911fb6eb 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -29,10 +29,27 @@ 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 *af_malloc(size_t size) { + mp_printf(&mp_plat_print, "af_malloc %lu\n", size); + mp_event_handle_nowait(); + return m_tracked_calloc(sizeof(uint8_t), size); +} + +void *af_realloc(void *p, size_t size) { + return NULL; +} - //mp_printf(&mp_plat_print, "Opening file %s\n", filename.data()); +void af_free(void *p) { + mp_printf(&mp_plat_print, "af_free\n"); + mp_event_handle_nowait(); + 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); + mp_event_handle_nowait(); mp_obj_t args[2] = { fn, @@ -43,34 +60,39 @@ file_io::file_io(std::string_view filename) { // 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]); + 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) { - //mp_printf(&mp_plat_print, "Reading %lu bytes\n", len); - mp_obj_t fhandle = this->state; +size_t fileio_read(void* fhandle, void *buf, size_t len) { + mp_printf(&mp_plat_print, "Reading %lu bytes\n", len); 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; + fileio_read((mp_obj_t)fhandle, &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 +100,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); } @@ -344,7 +361,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 { @@ -359,7 +377,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; } @@ -392,20 +410,21 @@ 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::wstring_view t((const wchar_t *)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)); } + self->vector->text(t, {(float)x, (float)y}, &tt); + return mp_const_none; } From 742c2cb8eb5f48dfb82c3f58800df65839672284 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 19 Apr 2024 12:28:11 +0100 Subject: [PATCH 09/35] PicoVector: Break things until they work. --- libraries/pico_vector/af-memory.h | 1 + libraries/pico_vector/alright-fonts.h | 47 +++++++++------ libraries/pico_vector/pico_vector.cpp | 17 ++++-- libraries/pico_vector/pico_vector.hpp | 21 ++++++- micropython/modules/picovector/picovector.cpp | 60 ++++++++++++++----- 5 files changed, 104 insertions(+), 42 deletions(-) diff --git a/libraries/pico_vector/af-memory.h b/libraries/pico_vector/af-memory.h index 81c647bda..22e01c909 100644 --- a/libraries/pico_vector/af-memory.h +++ b/libraries/pico_vector/af-memory.h @@ -2,4 +2,5 @@ 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, ...); } \ No newline at end of file diff --git a/libraries/pico_vector/alright-fonts.h b/libraries/pico_vector/alright-fonts.h index 701c7a078..f66cb230b 100644 --- a/libraries/pico_vector/alright-fonts.h +++ b/libraries/pico_vector/alright-fonts.h @@ -52,6 +52,10 @@ #define AF_FGETC(stream) fgetc(stream) #endif +#ifndef AF_DEBUG + #define AF_DEBUG(...) +#endif + #include "pretty-poly.h" #ifdef __cplusplus @@ -99,9 +103,9 @@ typedef struct { } af_text_metrics_t; bool af_load_font_file(AF_FILE file, af_face_t *face); -void af_render_character(af_face_t *face, wchar_t codepoint, af_text_metrics_t *tm); -void af_render(af_face_t *face, wchar_t *text, af_text_metrics_t *tm); -pp_rect_t af_measure(af_face_t *face, const wchar_t *text, af_text_metrics_t *tm); +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 @@ -146,6 +150,11 @@ bool af_load_font_file(AF_FILE file, af_face_t *face) { 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)); @@ -193,7 +202,7 @@ bool af_load_font_file(AF_FILE file, af_face_t *face) { return true; } -af_glyph_t *find_glyph(af_face_t *face, wchar_t c) { +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]; @@ -223,12 +232,12 @@ void af_render_glyph(af_glyph_t* glyph, af_text_metrics_t *tm) { for(uint32_t i = 0; i < poly.count; i++) { pp_path_t *path = &poly.paths[i]; - free(path->points); + AF_FREE(path->points); } - free(poly.paths); + AF_FREE(poly.paths); } -void af_render_character(af_face_t *face, wchar_t c, af_text_metrics_t *tm) { +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; @@ -236,10 +245,10 @@ void af_render_character(af_face_t *face, wchar_t c, af_text_metrics_t *tm) { af_render_glyph(glyph, tm); } -int get_line_width(af_face_t *face, wchar_t *text, af_text_metrics_t *tm) { +int get_line_width(af_face_t *face, const char *text, af_text_metrics_t *tm) { int line_width = 0; - wchar_t *end = wcschr(text, L'\n'); - for(wchar_t c = *text; text < end; text++, c = *text) { + 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; @@ -254,22 +263,22 @@ int get_line_width(af_face_t *face, wchar_t *text, af_text_metrics_t *tm) { return line_width; } -int get_max_line_width(af_face_t *face, wchar_t *text, af_text_metrics_t *tm) { +int get_max_line_width(af_face_t *face, const char *text, af_text_metrics_t *tm) { int max_width = 0; - wchar_t *end = wcschr(text, L'\n'); + 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 = wcschr(text, L'\n'); + end = strchr(text, '\n'); } return max_width; } -void af_render(af_face_t *face, wchar_t *text, af_text_metrics_t *tm) { +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; @@ -285,11 +294,11 @@ void af_render(af_face_t *face, wchar_t *text, af_text_metrics_t *tm) { caret.x = 0; caret.y = 0; - wchar_t *end = wcschr(text, L'\n'); + char *end = strchr(text, '\n'); while(end) { int line_width = get_line_width(face, text, tm); - for(wchar_t c = *text; text < end; text++, c = *text) { + for(char c = *text; text < end; text++, c = *text) { af_glyph_t *glyph = find_glyph(face, c); if(!glyph) { continue; @@ -320,7 +329,7 @@ void af_render(af_face_t *face, wchar_t *text, af_text_metrics_t *tm) { } text = end + 1; - end = wcschr(text, L'\n'); + end = strchr(text, '\n'); caret.x = 0; caret.y += line_height; @@ -331,12 +340,12 @@ void af_render(af_face_t *face, wchar_t *text, af_text_metrics_t *tm) { pp_transform(old); } -pp_rect_t af_measure(af_face_t *face, const wchar_t *text, af_text_metrics_t *tm) { +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 < wcslen(text); i++) { + for(size_t i = 0; i < strlen(text); i++) { af_glyph_t *glyph = find_glyph(face, text[i]); if(!glyph) { continue; diff --git a/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp index 8566e43fb..9c20f0735 100644 --- a/libraries/pico_vector/pico_vector.cpp +++ b/libraries/pico_vector/pico_vector.cpp @@ -56,9 +56,15 @@ namespace pimoroni { } } - pp_point_t PicoVector::text(std::wstring_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; @@ -69,7 +75,7 @@ namespace pimoroni { pp_point_t space; pp_point_t carriage_return = {0, -(PP_COORD_TYPE)text_metrics.line_height}; - wchar_t spc = L' '; + char spc = ' '; space.x = af_measure(text_metrics.face, &spc, &text_metrics).w; if (space.x == 0) { @@ -110,10 +116,10 @@ namespace pimoroni { } for(size_t j = i; j < std::min(next_break + 1, text.length()); j++) { - if (text[j] == L'\n') { // Linebreak + if (text[j] == '\n') { // Linebreak caret = pp_point_sub(&caret, &carriage_return); carriage_return = initial_carriage_return; - } else if (text[j] == L' ') { // Space + } else if (text[j] == ' ') { // Space caret = pp_point_add(&caret, &space); carriage_return = pp_point_add(&carriage_return, &space); } else { @@ -125,7 +131,7 @@ namespace pimoroni { af_render_character(text_metrics.face, text[j], &text_metrics); } pp_point_t advance = { - (PP_COORD_TYPE)af_measure(text_metrics.face, (const wchar_t *)text[j], &text_metrics).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); @@ -137,5 +143,6 @@ namespace pimoroni { } return {caret.x, caret.y}; +*/ } } \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index fd1532b0f..d5f442244 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -14,6 +14,8 @@ #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" @@ -40,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) { @@ -86,17 +97,21 @@ namespace pimoroni { } bool set_font(std::string_view font_path, unsigned int font_size) { + 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()); - af_load_font_file(font, text_metrics.face); - bool result = false; + bool result = af_load_font_file(font, text_metrics.face); set_font_size(font_size); return result; } - pp_point_t text(std::wstring_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); diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index d911fb6eb..08daca658 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -10,6 +10,7 @@ extern "C" { #include "py/stream.h" #include "py/reader.h" #include "extmod/vfs.h" +#include typedef struct _ModPicoGraphics_obj_t { mp_obj_base_t base; @@ -29,10 +30,31 @@ typedef struct _PATH_obj_t { pp_path_t path; } _PATH_obj_t; +void __printf_debug_flush() { + for(auto i = 0u; i < 10; i++) { + sleep_ms(1); + mp_event_handle_nowait(); + } +} + +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); - mp_event_handle_nowait(); - return m_tracked_calloc(sizeof(uint8_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) { @@ -40,16 +62,16 @@ void *af_realloc(void *p, size_t size) { } void af_free(void *p) { - mp_printf(&mp_plat_print, "af_free\n"); - mp_event_handle_nowait(); + //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); - mp_event_handle_nowait(); + //mp_printf(&mp_plat_print, "Opening file %s\n", filename); + //__printf_debug_flush(); mp_obj_t args[2] = { fn, @@ -58,10 +80,10 @@ void* fileio_open(const char *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); - int filesize = mp_obj_get_int(tuple->items[6]); - mp_printf(&mp_plat_print, "Size %lu\n", filesize); + //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); @@ -73,14 +95,17 @@ void fileio_close(void* fhandle) { } size_t fileio_read(void* fhandle, void *buf, size_t len) { - mp_printf(&mp_plat_print, "Reading %lu bytes\n", len); + //mp_printf(&mp_plat_print, "Reading %lu bytes\n", len); + //__printf_debug_flush(); int error; return mp_stream_read_exactly((mp_obj_t)fhandle, buf, len, &error); } int fileio_getc(void* fhandle) { unsigned char buf; - fileio_read((mp_obj_t)fhandle, &buf, 1); + //mp_printf(&mp_plat_print, "Reading char\n"); + //__printf_debug_flush(); + fileio_read(fhandle, (void *)&buf, 1); return (int)buf; } @@ -410,7 +435,7 @@ 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::wstring_view t((const wchar_t *)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; @@ -423,7 +448,12 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) pp_mat3_rotate(&tt, mp_obj_get_float(args[ARG_angle].u_obj)); } - self->vector->text(t, {(float)x, (float)y}, &tt); + 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; } From 739c77cdde4709686b81b9bfbd625436aa64d8b3 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 5 Jun 2024 12:40:57 +0100 Subject: [PATCH 10/35] PicoVector: fix pointer arithmatic in af_load_font_file. Pointers were being incremented as if they were bytes, rather than larger containers. --- libraries/pico_vector/alright-fonts.h | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/libraries/pico_vector/alright-fonts.h b/libraries/pico_vector/alright-fonts.h index f66cb230b..655f890a8 100644 --- a/libraries/pico_vector/alright-fonts.h +++ b/libraries/pico_vector/alright-fonts.h @@ -146,18 +146,20 @@ bool af_load_font_file(AF_FILE file, af_face_t *face) { uint16_t path_count = ru16(file); uint16_t point_count = ru16(file); + size_t glyph_buffer_size = sizeof(af_glyph_t) * glyph_count; + size_t path_buffer_size = sizeof(af_path_t) * path_count; + size_t point_buffer_size = sizeof(af_point_t) * point_count; + // 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); + uint8_t *buffer = (uint8_t *)AF_MALLOC(glyph_buffer_size + path_buffer_size + point_buffer_size); 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)); + af_path_t *paths = ( af_path_t *)(buffer + glyph_buffer_size); + af_point_t *points = (af_point_t *)(buffer + glyph_buffer_size + path_buffer_size); // load glyph dictionary face->glyph_count = glyph_count; @@ -172,7 +174,7 @@ bool af_load_font_file(AF_FILE file, af_face_t *face) { glyph->advance = ru8(file); glyph->path_count = ru8(file); glyph->paths = paths; - paths += sizeof(af_path_t) * glyph->path_count; + paths += glyph->path_count; } // load the glyph paths @@ -182,7 +184,7 @@ bool af_load_font_file(AF_FILE file, af_face_t *face) { af_path_t *path = &glyph->paths[j]; path->point_count = ru8(file); path->points = points; - points += sizeof(af_point_t) * path->point_count; + points += path->point_count; } } From 58dcc62de0d01bf7f96271c426a8b1fc3f856fb5 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 5 Jun 2024 14:16:24 +0100 Subject: [PATCH 11/35] PicoVector: Fix out of bounds drawing. pretty-poly.h is not giving us fully clipped rectangles, so revert to the slower bounds checked pixel for now. --- libraries/pico_vector/pico_vector.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index d5f442244..1d96aabdf 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -63,11 +63,12 @@ namespace pimoroni { (uint8_t)_pp_antialias)) { return; } + for(auto y = 0; y < tile->h; y++) { for(auto x = 0; x < tile->w; x++) { uint8_t alpha = *tile_data++; if (alpha >= 4) { - PicoVector::graphics->set_pixel({x + tile->x, y + tile->y}); + PicoVector::graphics->pixel({x + tile->x, y + tile->y}); } else if (alpha > 0) { alpha = alpha_map[alpha]; PicoVector::graphics->set_pixel_alpha({x + tile->x, y + tile->y}, alpha); @@ -80,7 +81,7 @@ namespace pimoroni { for(auto x = 0; x < tile->w; x++) { uint8_t alpha = *tile_data++; if (alpha) { - PicoVector::graphics->set_pixel({x + tile->x, y + tile->y}); + PicoVector::graphics->pixel({x + tile->x, y + tile->y}); } } tile_data += tile->stride - tile->w; From 469833670ee73a662f98e30d9e71b9f36f1eac16 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 5 Jun 2024 14:44:35 +0100 Subject: [PATCH 12/35] PicoVector: render text that doesn't end with a linebreak. --- libraries/pico_vector/alright-fonts.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/pico_vector/alright-fonts.h b/libraries/pico_vector/alright-fonts.h index 655f890a8..e598764ba 100644 --- a/libraries/pico_vector/alright-fonts.h +++ b/libraries/pico_vector/alright-fonts.h @@ -250,6 +250,7 @@ void af_render_character(af_face_t *face, const char c, af_text_metrics_t *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'); + if (!end) end = (char *)text + strlen(text); for(char c = *text; text < end; text++, c = *text) { af_glyph_t *glyph = find_glyph(face, c); if(!glyph) { @@ -297,7 +298,9 @@ void af_render(af_face_t *face, const char *text, af_text_metrics_t *tm) { caret.y = 0; char *end = strchr(text, '\n'); - while(end) { + if (!end) end = (char *)text + strlen(text); + + while(true) { int line_width = get_line_width(face, text, tm); for(char c = *text; text < end; text++, c = *text) { @@ -331,7 +334,9 @@ void af_render(af_face_t *face, const char *text, af_text_metrics_t *tm) { } text = end + 1; + if (*text == '\0') break; end = strchr(text, '\n'); + if (!end) end = (char *)text + strlen(text); caret.x = 0; caret.y += line_height; From 8a719c214e7258a8e7cea9b79909a4185ce9893d Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 11 Jun 2024 14:39:06 +0100 Subject: [PATCH 13/35] PicoVector: C++ basic bringup. --- .gitmodules | 3 - examples/pico_display_2/CMakeLists.txt | 15 +- .../pico_display_2/pico_display_2_demo.cmake | 10 ++ .../pico_display_2_vector.cmake | 41 ++++++ .../pico_display_2/pico_display_2_vector.cpp | 66 +++++++++ .../pico_display_2/vector/DynaPuff-Medium.af | Bin 0 -> 9688 bytes libraries/pico_vector/af-file-io.c | 36 +++++ libraries/pico_vector/af-file-io.h | 6 +- libraries/pico_vector/af-memory.c | 24 ++++ libraries/pico_vector/af-memory.h | 8 +- libraries/pico_vector/alright_fonts.cpp | 136 ------------------ libraries/pico_vector/alright_fonts.hpp | 74 ---------- libraries/pico_vector/pico_vector.cmake | 3 +- libraries/pico_vector/pico_vector.hpp | 15 +- 14 files changed, 207 insertions(+), 230 deletions(-) create mode 100644 examples/pico_display_2/pico_display_2_demo.cmake create mode 100644 examples/pico_display_2/pico_display_2_vector.cmake create mode 100644 examples/pico_display_2/pico_display_2_vector.cpp create mode 100644 examples/pico_display_2/vector/DynaPuff-Medium.af create mode 100644 libraries/pico_vector/af-file-io.c create mode 100644 libraries/pico_vector/af-memory.c delete mode 100644 libraries/pico_vector/alright_fonts.cpp delete mode 100644 libraries/pico_vector/alright_fonts.hpp diff --git a/.gitmodules b/.gitmodules index 4861dad5d..b1f258d23 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,6 +25,3 @@ [submodule "drivers/mlx90640/src"] path = drivers/mlx90640/src url = https://github.com/melexis/mlx90640-library -[submodule "libraries/pico_vector/pretty_poly"] - path = libraries/pico_vector/pretty_poly - url = https://github.com/lowfatcode/pretty-poly/ diff --git a/examples/pico_display_2/CMakeLists.txt b/examples/pico_display_2/CMakeLists.txt index d40b117e7..8c72a3751 100644 --- a/examples/pico_display_2/CMakeLists.txt +++ b/examples/pico_display_2/CMakeLists.txt @@ -1,14 +1,3 @@ add_subdirectory(mandelbrot) - -set(OUTPUT_NAME pico_display2_demo) - -add_executable( - ${OUTPUT_NAME} - pico_display_2_demo.cpp -) - -# Pull in pico libraries that we need -target_link_libraries(${OUTPUT_NAME} pico_stdlib hardware_spi hardware_pwm hardware_dma rgbled button pico_display_2 st7789 pico_graphics) - -# create map/bin/hex file etc. -pico_add_extra_outputs(${OUTPUT_NAME}) \ No newline at end of file +include(pico_display_2_demo.cmake) +include(pico_display_2_vector.cmake) \ No newline at end of file diff --git a/examples/pico_display_2/pico_display_2_demo.cmake b/examples/pico_display_2/pico_display_2_demo.cmake new file mode 100644 index 000000000..a975866b0 --- /dev/null +++ b/examples/pico_display_2/pico_display_2_demo.cmake @@ -0,0 +1,10 @@ +add_executable( + pico_display_2_demo + pico_display_2_demo.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(pico_display_2_demo pico_stdlib hardware_spi hardware_pwm hardware_dma rgbled button pico_display_2 st7789 pico_graphics) + +# create map/bin/hex file etc. +pico_add_extra_outputs(pico_display_2_demo) \ No newline at end of file diff --git a/examples/pico_display_2/pico_display_2_vector.cmake b/examples/pico_display_2/pico_display_2_vector.cmake new file mode 100644 index 000000000..1459902bd --- /dev/null +++ b/examples/pico_display_2/pico_display_2_vector.cmake @@ -0,0 +1,41 @@ +function(static_asset NAME PATH) + get_filename_component(PATH ${PATH} ABSOLUTE) + get_filename_component(ASSET ${PATH} NAME) + get_filename_component(PATH ${PATH} DIRECTORY) + set(OBJNAME ${ASSET}.o) + add_custom_command(OUTPUT ${OBJNAME} + DEPENDS ${PATH}/${ASSET} + COMMENT "Building ${OBJNAME}" + WORKING_DIRECTORY "${PATH}" + COMMAND ${CMAKE_LINKER} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/${OBJNAME} ${ASSET} + COMMAND ${CMAKE_OBJDUMP} -t ${CMAKE_CURRENT_BINARY_DIR}/${OBJNAME} + ) + # TODO figure out how to make static resources work + ## COMMAND ${CMAKE_OBJCOPY} --rename-section .data=.rodata,alloc,load,readonly,data,contents ${CMAKE_CURRENT_BINARY_DIR}/${OBJNAME} ${CMAKE_CURRENT_BINARY_DIR}/${OBJNAME}) + target_sources(${NAME} PRIVATE ${OBJNAME}) +endfunction() + +add_executable( + pico_display_2_vector + pico_display_2_vector.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(pico_display_2_vector + pico_stdlib + hardware_spi + hardware_pwm + hardware_dma + pico_display_2 + st7789 + pico_graphics + pico_vector + ) + +static_asset(pico_display_2_vector ${CMAKE_CURRENT_LIST_DIR}/vector/DynaPuff-Medium.af) + +pico_enable_stdio_usb(pico_display_2_vector 0) +pico_enable_stdio_uart(pico_display_2_vector 1) + +# create map/bin/hex file etc. +pico_add_extra_outputs(pico_display_2_vector) \ No newline at end of file diff --git a/examples/pico_display_2/pico_display_2_vector.cpp b/examples/pico_display_2/pico_display_2_vector.cpp new file mode 100644 index 000000000..68e915542 --- /dev/null +++ b/examples/pico_display_2/pico_display_2_vector.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include + +#include "libraries/pico_display_2/pico_display_2.hpp" +#include "drivers/st7789/st7789.hpp" +#include "libraries/pico_graphics/pico_graphics.hpp" +#include "libraries/pico_vector/pico_vector.hpp" + + +using namespace pimoroni; + +ST7789 st7789(320, 240, ROTATE_180, false, get_spi_pins(BG_SPI_FRONT)); +PicoGraphics_PenRGB332 graphics(st7789.width, st7789.height, nullptr); + +uint8_t vector_mem[PicoVector::pretty_poly_buffer_size()]; + +PicoVector vector(&graphics); + +extern char _binary_DynaPuff_Medium_af_start[]; +extern size_t _binary_DynaPuff_Medium_af_size; + +int main() { + stdio_init_all(); + + Pen BG = graphics.create_pen(120, 40, 60); + Pen TEXT = graphics.create_pen(255, 255, 255); + + st7789.set_backlight(255); + + vector.set_font(_binary_DynaPuff_Medium_af_start, 30); + + unsigned int a = 0; + + while (true) { + Point text_location(0, 0); + graphics.set_pen(BG); + graphics.clear(); + graphics.set_pen(TEXT); + graphics.text("Hello World", text_location, 320); + + pp_point_t outline[] = {{-64, -64}, {64, -64}, {64, 64}, {-64, 64}}; + pp_point_t hole[] = {{ -32, 32}, { 32, 32}, { 32, -32}, { -32, -32}}; + pp_path_t paths[] = { + {.points = outline, .count = 4}, + {.points = hole, .count = 4} + }; + pp_poly_t poly = {.paths = paths, .count = 2}; + + pp_mat3_t pos = pp_mat3_identity(); + pp_mat3_translate(&pos, 50, 50); + pp_mat3_rotate(&pos, a); + vector.draw(&poly); + vector.text("Hello World", &pos); + + // update screen + st7789.update(&graphics); + a += 1; + if (a > 359) { + a = 0; + } + } + + return 0; +} diff --git a/examples/pico_display_2/vector/DynaPuff-Medium.af b/examples/pico_display_2/vector/DynaPuff-Medium.af new file mode 100644 index 0000000000000000000000000000000000000000..5a935f3fa34df238fb51d6e8bd62edf6db4b3478 GIT binary patch literal 9688 zcma)hXL~AXnqE~wkuz0A&N+hsp?qSWb9#Dudg9KmckT7A?R8$~9Daa*&UBxkoDo?F z5d{(m1j+>E04l%g8L$0mFJF8>RjBHH-zVMob3cFaSGpGv1pPVmzl0`83;u${5Trv$ z@z+uW(j!)b)`CC=gnawqJpvh#yTC6;D3A#uU%h*aLS_m%_5L*lvLIHzRt!T{^ji1F zRv5Cu;M)vCc3A0fx?#wHupBNA3^|c&Oe=*U7a}tYZ7}3UNK68*J@B>XkB4B$OM%DU zyh0!!Ose0SU}y*-J#YOmPM-eFad=2goBV^#K5d;dsr0cC0hDIpl z@K+NQX!H;F$H4n1!8yjky6?gF1kAFzoiH?s@EjHo0!{tl`UOlnzjh(eOUmW1fBUBh z^a>$IK1?FeYZMB8n4~~&DCF3?7cle|d3y2nTLk(FC4C=8VCWq>aD4##cuyf;egOAB zpy0XJF!VJ_!M;+%&`)4;?D;+4pycp}Nf`PmN_yW9!_c=VIrZTK4E+oxM?buQq3o;E`(9cma@NNu&enENq`olL==$Fs$dx=25LZHBVu->m>GW1~#hJJ&R!S`T4e}sVN zr(o!}DEZZgpTW={BjoE3?_lUpU~=LE`2Fu7U|sP4e~RAdertiDKl_87|1SLd>%WH> z%nk<({W)?H+`gHvGN{|O=APksYK{~0F7r@;OHf}D?hHvvQc73_Nw z*zLbjZ(P3xe){i>Gr?bKVCaAR;rf442)oD&bH#k#pRt2XCdR-R;ul;t-(>QcMSPw_ zi)q|JJI`69+Gwua94yzG#nj{hi=hpf_~meQ-b7Av0-uFkjhDtZh?A+f{ZY zZRwe`C%Go>@xGYUH4RN`!&$MHECoYOU67Y$Rarwezz2dp>w$Jhxkh@FHtmdaqBzr> z8IE;_>XxJ-*k@H~WonkXP1|I~*b%`xv8q_sFPWDeE1tEX@Nhhs97;Pgri^k+n!$2- zQByYWI`@6ez^T7I)bm_AZ)|s_M{QrhkkJVghb0f%dp)e9s%eTf*_>=z`C0qX@YytN znKP{lb~roCA}vqdMiNMrx=veRE(+$v^O8mRigH~OS8pma^1P%d+!Af!aq)(5jTh#` zxoJP-ft*jazUAI@Vd_{;ZAZ?QzcG9;)|xzear(OV>iWgqWPj`_a2vWA?TxepC%!XJ z&wC?;IS{+gxfAqMyOy|n$u}RI9ho0n8VLuI?!39KJChD@QU>FY2;x1l9~pPFYw88% zlyZnR7*$RMD`FL4nOEfGSP52)y}?}*EEDtE8RM*N-nHag4aEE@-eH*W!t{{ z&~fB!`u0Y4M$41CFKSbb@gsl7e>U8ixO(~UVlY9DLc@@6V83zR*zYZUe{L?$C1dG&9Z# z^OlA4*o@?}_IuqA#!u#H)0}o$5tbydZSk(SiJ!}^w0%8krkYR#L(P^k#i$SlR(N1u z(>t_dLrve*ooGAiE5*I^35P{gKApj)u_z1}g%QSpeb2uXpW^%2jxfWG(O0NT$PDrs z{mA;B_fb4e%*&P)>*|;`t;=fv_j-0IlLsSu@C4CT8&=e4ql zB*xg_M~Nhs62*i|!g=uw@kKf{eOwd<_f>&62`Ls?g~G;Q^T;#zt`+=wm(=Yk{A zzPKT1GFxbq(m<+|3a!K{a7g+SsjCh8D3*pmq5(^+gfgN|~Z*oTZm zY7?nZcUdKIQB}|sjU~&jxoT`^n#3XJgm)@x6W92iXdw8pYEhq%o8P~W#zvUHelYEd zb;{qz^0I_}&M;&7vJxI?#0uU31%d>No^;C^>T0 zjCoU+P$w0evJCLWE?$=$%1#w+;H^c~sye30YIijU>NC}q@=*qSE+F|2yc_Nbx5?Y( z=R^r{LYU%YXjw`Y-b51A2xE=A%wLo(s27dPwslw3x8==wiq49yW@>7W73abWQCHU0 z_RN=#YtOxFV1*2@6cR$LC)xnm_y)N`yR0+Ofn;B?t1fACnyhL|l463oio_}4?Yw3A zymrB|Y+rMQJyBoEn|aPPOGRB%SM~drhNI~|@^w7du3P(qrEefLkObyKOcL!=ZmE~d zE^#5h(44E!lqXnA(B#y(dxA1vk`**t%9JF*Owm%52xWt|##t6GXc09mC#Cn&8%0~u zA-e1yt&5z)$H)P7m!9Lqc@fbXu_Ry6&zk0}3yvk%iYMYuJ9iv)`@W|Z*dI9xodnwM zuB`(;m-buRgPFwYL|s+`X9Fv$lFGO&L8QbPL6N^J*!OODKrEX#MM*r4ZHbGb3V%ns zqbeJAtu^O?_sr9AgW77pH}?%s|L?gdSF{UOS8^e{Q1!HD>J#}P(c-q4En0(CrR8WT zMwq$6SrpFe7OjiU<)Kw?*d4Q_j2TH0FRBZ=qN!vnyLP>`p?aV-d>ZToE_^rcdq>~$ zWQ0_}I~c@&V)q&M=ndr(IRnVI$IJ`k#D;Q3zi6Lx%zGA94M|gUBs>Lq(NkWiFAQDF ziTT*jJS2l^HJOk9=a^>IT|vdP_~rQi*C zow7<_)XbUY?Tg-(KzL*`v>nX*cf7myefyrP=Boz|h7U*Eq07Ln_t8!&VF}>ne3CO@ zJOWJXQqIw1Mw3&=_wc%KpIc?^(o5(Tnxe&-8=N)%ig;17VBNB$jY)M}n#4B68F3ab zD2wW%F|TeaTEvm~4C_cQR2P~HQ`dHCIX1MYKQa$)SJG8vsXB18(`66FO&_6}o*S^#V&L5W8@PwZ_I$a>iYyTYhYcaR)4!%Fc|SkjQN zBpqp2#+kJjO*`7Mtip^4VqmSbF>l$i?^*XvP4%g?E4pOd&~7MKa2IK#r|ctPQ(M*6 zq4<*O^Q`QNy#jXo?i5*2wwWZvY#Mud2 z9Ered$TDr7J;Qil+yQ2HDe7StQh;poM+y1N5|~deu9ahCYglqR4sekB#Js0nQ9G0- zWuIE1=UH3axF}4lC>C_H<}cn)fzP8~re>Rt0+$bJ210&>web6{L1)8>3v?<0IYnbgk_nZzL1*?Iwui(i!3-%rBp5?%D zY&&BCP7ToMnsQEU(kskuUR1b>FUV)L(}o%IoPEKyJQN9}L*>!d_~qE`=tF1_Bz>?O zvOig_EOp1R`BZxckO#h24qzd`P>e-!oV21#8xnG{m$KRSe*FnkY)jiai;G zLqZa;j6VIAc8MOKWm<+2Wv_6S_zT#aY}PpKnDxyMuK<>u7~TwI93@A|y)#q^Rzvl% zqtVmhp6|x~XdqRWh6_u_acpZD8|r;o18-p`*tw*q%4u_ktYyodu*QrV+OTRv8pTt1 zTC#11wUC^|`+|G+6{CxuP-*~6^NchnCR`_$l?#?x=bUe0cxh~PDmIxMO9!^y8RMoV zt=N_nKy_&Vq&!ic8QSKy|15Yu3K-q^-N<8b;3dTf_s7nJ@<@3=?^su&3&AzN%RAy# zd0T>rJVHcyF?I}%AgjnSy2zO4&Pu1%Gsd~+iZ{Fg{Jr&R=VkRpV*()e^Z8u&R=~?t zkiKJT+fJQLPsNpYWUMJ;To={ERVmfBs;p{i&dt;I8TYJj&c86c5?uFhcq6WuDUQYP zD87Xi#RXZxlC$SMMSsOtbJk4_Q5~yc2lz1#Vg5+u8B;g4cojp}uEehIEh| zCO~y%f-IqR(S5W?OVcB)72YB?rvR+i0_wkeVQ9_wj9qbSLYu^rcnmLM1!+-LGM8Od zU&C|gJhq3wOrUQ++A;b=*U+W zPK#!UIoXOdEJF7H6z7mf7?CFJQHK>9TC+ zxqHJN_ydp)pxOZLzDZwUEU;(vF)S%c@&JeDtcvHQbJ`i}v}ek;*U9_ z4#2lODc`og6s!jh{b#x?fu5AZV$jVnAE?*x3DTri8D(yfR}z)6s`%(RM-s&>_7W6ja>o(8+4ccFgZ_cj@r9`rYWYo8Mh@HxeM*sb_c_#}DW z8OeA)>j_!~aj*tDzydbiJJo20jod>%9OW*A?KhZ-f zXao*I{wLp~_tJal+x5r%G4GbU;HX&l4F}qWa!*zO(I3S(iMpgqT;cbkKBzTM%s%}A z?Sj~=(6$&Lp1Jd)8GKH@q~0)Qjaf_FvFZUQ8=e`Tf3f~D`KtK3@uvIw`qll5{v;`! z#Xx_mSl6Y@CD)1j#MyA<91&psg`w%-%-F*C#$;})K6yHRHToDJy`-yey)||B-W&SNys0y^*e9b;yFpK%^Ocrkq}c16RsNEAK{}@?=}N|;E$c|x;^v4xtlp5vBuOG8 z$;(TBbBbH`|LIqD&>Ulv6BPpPte7{?*k)aGpbK9bU6}~KjJ^g6$h+K!^1H^HvzNWG z+t9uL(ed;Sc?C@jMs8nr-d+CW_S?sA$gd&and8{fH1At>9c5R=R}CGS&u!n z8QL8#1#-SESJE8QCKQ{JEM5V;`%r!QH|IEc_B}nzNCCFAB3hKpD`rigdU~ICvNoBV z*c!`@6oX~ouB&R>*YE4=)|Tfua6a0byq$O$9rz(PX?f6INDhbt;zujt1E9DRj0J1X zvF%DZ6SjzcUG;2J^vE}@GVwvA;|!_qTfo9+#L9bnEfNJ4^rKZq_w zZSf(tM{LVt@`ND8NikE@1RO?I(M9GQZ&nRBws63?V?0-bJm@Ianadz!X7PFHl6)1f z;Rm3qpUBU(ZGGE(VmY+ctVQ#xdBHtBG#gwTSskm5wnhL_`>#C@j)8^rgFFd6SLBVc z#AtT7;%|D|_G?pL3n@XrA?^$BxtGjSS{*RO3=&1clofQ5KF^uq%?jp4i{ceLD#^*J z>Ylo%J+f@t!;U4-EDCrxZH=+~W1X7E7Kv49l&BG>l2chtl?S;Ru|#cg*OspwIvqI; z)rL2QS4S4cW?xLdnt3z#)#CfruM;1#@2l^Qzq)??I7N=ZAt=!I-Z>E;L*LLIsm~P`(p&sNG`BU5Ps4W9g1;S5RbTfX~+973ua04eN$cXq4a=%)J_r=Y@x{s2iST%IG%=hW+IHt{CEc#RY(BPLS?DhmNi_cK4f;H9P6{H_HshTG{(Y|eX=l+|Q8xuW!3Ew^whia5 zQ{Q>88@e36@q;c+4~bwN!~$I(^^sul87!26&`I>>8C}pgz_>-$Ecc7>ll*(lN8=as ztZ81qtXLJTh?hVgyeB*tkUYTDR1ca*L*EP;DLR^(;bdDF28y~*w1o{$k&!~zkp=h* z@)7->`H4R#T*NozTiUX*W$QZc-H;dc(1qV)Kgd35<}{1Ch+#*6sOxAS)ua-Up*Wq( zqB9VnW5E!0zfE(lngb+iV4H2Ro$97Y>&9(flM$T zs*QF=ZiWFm0j2TEcxi2$&veJCBgwJ&Ow<;2@yloYr#(`8)CQWQFEVD>pZGrrKVn}b zi$oO9it|`Tnp6S(%@%dXhtk8jvHIvC=xacRd+(in8)<+vkn%~^C+@`;*p=u}Kx!^^ zmxi9H4Yb;ZwPx8h?HCH0tTHDn0F19-4eSsaQoM}3Sj_Vw7xL)9D=+51@k`iDq z?tpoZUV_eT4+UAmO-iE5xB+yrmW(NGoHI^aKG}b80<|r;6x59|V6 zjrGcYWd(Y+exN4Rmq3^8n$N5UmVNV{v8*qt^NND3Bq`&2UR0pDT}|7e=~#UtI}xKq6AqWpK-E2G8#McT5u|UKY(77m+Mr9*1iVD#| zM$$a6-B`Q!uJy`vtG`!2s=C^);T%+@eaoJC$5_wey0vEB(dRV<6;N7ad(x)# zNPZ)`mplQT8{&|F6<*Q0)B-g{jnI}EbF9z&PlC_bv}A!;!ocK#9j33L3$$6*7k*rj z5N%@Hco8swGyGC~5AYFYK@5QL=neG**y+Ffih2<=7N2Q0Yeop^Vc}f zx!I>SsC#IdzC>SOeG!1%#J@1Jm&{i2UzzW|Beh_~m zW+bc91X02dMAzIa-le!JYb(3z3+=V$R`nnqV9-$3pAID^qHn?h^W-?ZH>ox4W-PQ~9w{Xm7X~JPzCqJvyI&j?gz< zgDO_i=G9p+`zZqyzrr4XMgXx$VBKr#0h*yDSP|YjKP-v@Hk1W?Xj77qM*(|Zv#dKd z+%a#;pA)PJ*D;`x5!(dNkY#Zdn3-5sZEMc3Co&Wd09|pU2$)H84Ee8n{RY zX&qQ@EnT376m{ER{i38s9D}~$Ry^Q7<5wTOr}of2Fi}Z?>DY!El0!Ht>;ng1(|Xi% z$^oTDEz=9E3^yf=5fSyeK5ULSVxE*gGn^kOk2l7Sp6le$wfo-QH$Q25x}LEED$23F zZmU|$W}rf9wv|~Kn0eu4td6ztW67DU1C-0KIF4_Lx3M&ll*LpL<2tD48!nK^{&X-K zDvs`t90$8YH|_`1lkQ11kU!#A#1-)z1myYyy)*kJur!@vJN>#eU_uvH_0pTni;I26TJw z^NbAWHb56^J?mn$JR`$S@?ye>WL*xl7H!m!0CjfDlCniWMFW-}He1w@bY;OrW7k%*Gz?8$Q`1maR7J(MG)L@8+p-(UBSs2;pW`>Q9(t?+&so-g zv|!4Uc5U0TifvgI&xwmzS+c9#)mLqGcgxrIT)F_SHatl|Zm^!vN6Izj3hl8v@}8=v zJAc;OC5OU8_5rO%DN?iaO-@|6frrIl8Uwn-FUpUa&$?;DoPJ3g)`V?qj&;w5KQXlF z*|z2YFU#n54JXDc!<}xRAr*l8;!mP`;kB?MJQUTi3;~dqU7+QWZ8(L*Xd9gMA<{!S z`}TYLrTx-!Ykbs@QbR$JT1JTgd za3NUrA9_2^YwLsI$L@h3#RJYI;~YJp>>!(z7;POXAvsDKP0*uY+PEQziBjTiaT{yn z$3#QACogF-rlfV#ne*+2&cP=gN{&TduDxD=yYVjiKJoSThn@G0cc))ny}5fa7=;7m z&;U${t^q2XnyaQN@au`Dqq>scNx<}7S^+znBj z*pe4@N4g_@%~Y_aoiYDfV0~m`EIGOrD*6vRXZ9P@la{JrNqCei$_3hCoU#uE4SY|r zW8OArYlibl@H^2D*k@vnSdqk~bxDVKA{ZD?D8jxceo%e_bIlD?!BVglT|3^Y zXWw;TZ5dmJw&77nsu2Y$q4NPa(&5nh^A;?Q0KMS$BYx8pEM~4XpOPU GD*tatPE3mc literal 0 HcmV?d00001 diff --git a/libraries/pico_vector/af-file-io.c b/libraries/pico_vector/af-file-io.c new file mode 100644 index 000000000..718f528a0 --- /dev/null +++ b/libraries/pico_vector/af-file-io.c @@ -0,0 +1,36 @@ +#include "af-file-io.h" +#include "string.h" + +static size_t ptr = 0; + +void* fileio_open(const char* filename) { + ptr = 0; + return NULL; +} + +void fileio_close(void* fhandle) { + ptr = 0; + return; +} + +size_t fileio_read(void* fhandle, void *buf, size_t len) { + memcpy(buf, fhandle + ptr, len); + ptr += len; + return len; +} + +int fileio_getc(void* fhandle) { + uint8_t *f = fhandle; + int c = f[ptr]; + ptr += 1; + return c; +} + +size_t fileio_tell(void* fhandle) { + return ptr; +} + +size_t fileio_seek(void* fhandle, size_t pos) { + ptr = pos; + return ptr; +} \ No newline at end of file diff --git a/libraries/pico_vector/af-file-io.h b/libraries/pico_vector/af-file-io.h index eb5a413b6..3070f7ae9 100644 --- a/libraries/pico_vector/af-file-io.h +++ b/libraries/pico_vector/af-file-io.h @@ -1,7 +1,9 @@ #include #include +#ifdef __cplusplus extern "C" { +#endif void* fileio_open(const char* filename); void fileio_close(void* fhandle); @@ -13,4 +15,6 @@ int fileio_getc(void* fhandle); size_t fileio_tell(void* fhandle); size_t fileio_seek(void* fhandle, size_t pos); -} \ No newline at end of file +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/libraries/pico_vector/af-memory.c b/libraries/pico_vector/af-memory.c new file mode 100644 index 000000000..5aa20aa27 --- /dev/null +++ b/libraries/pico_vector/af-memory.c @@ -0,0 +1,24 @@ +#include "af-memory.h" +#include +#include +#include + +void *af_malloc(size_t size) { + return malloc(size); +} + +void *af_realloc(void *p, size_t size) { + return realloc(p, size); +} + +void af_free(void *p) { + free(p); +} + +void af_debug(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int ret = printf(fmt, ap); + va_end(ap); + (void)ret; +} \ No newline at end of file diff --git a/libraries/pico_vector/af-memory.h b/libraries/pico_vector/af-memory.h index 22e01c909..f522d2cc9 100644 --- a/libraries/pico_vector/af-memory.h +++ b/libraries/pico_vector/af-memory.h @@ -1,6 +1,12 @@ +#include + +#ifdef __cplusplus extern "C" { +#endif 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, ...); -} \ No newline at end of file +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/libraries/pico_vector/alright_fonts.cpp b/libraries/pico_vector/alright_fonts.cpp deleted file mode 100644 index b276b1ee6..000000000 --- a/libraries/pico_vector/alright_fonts.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "alright_fonts.hpp" - -namespace alright_fonts { - /* - utility functions - */ - pp_rect_t measure_character(text_metrics_t &tm, uint16_t codepoint) { - if(tm.face.glyphs.count(codepoint) == 1) { - glyph_t glyph = tm.face.glyphs[codepoint]; - - return {0, 0, ((glyph.advance * tm.size) / 128), tm.size}; - } - - return {0, 0, 0, 0}; - } - - /* - render functions - */ - - void render_character(text_metrics_t &tm, uint16_t codepoint, pp_point_t origin, pp_mat3_t *transform) { - if(tm.face.glyphs.count(codepoint) == 1) { - glyph_t glyph = tm.face.glyphs[codepoint]; - pp_transform(transform); - pp_render(&glyph.contours); - } - } - /* - load functions - */ - - // big endian stream value helpers - uint16_t ru16(file_io &ifs) {uint8_t w[2]; ifs.read((char *)w, 2); return w[0] << 8 | w[1];} - int16_t rs16(file_io &ifs) {uint8_t w[2]; ifs.read((char *)w, 2); return w[0] << 8 | w[1];} - uint32_t ru32(file_io &ifs) {uint8_t dw[4]; ifs.read((char *)dw, 4); return dw[0] << 24 | dw[1] << 16 | dw[2] << 8 | dw[3];} - uint8_t ru8(file_io &ifs) {uint8_t w; ifs.read(&w, 1); return w;} - int8_t rs8(file_io &ifs) {int8_t w; ifs.read(&w, 1); return w;} - - bool face_t::load(file_io &ifs) { - char marker[4]; - ifs.read(marker, sizeof(marker)); - - // check header magic bytes are present - if(memcmp(marker, "af!?", 4) != 0) { - // doesn't start with magic marker - return false; - } - - // number of glyphs embedded in font file - this->glyph_count = ru16(ifs); - - // extract flags and ensure none set - this->flags = ru16(ifs); - if(this->flags != 0) { - // unknown flags set - return false; - } - - // extract glyph dictionary - uint16_t glyph_entry_size = 9; - uint32_t contour_data_offset = 8 + this->glyph_count * glyph_entry_size; - for(auto i = 0; i < this->glyph_count; i++) { - glyph_t g; - g.codepoint = ru16(ifs); - g.bounds.x = rs8(ifs); - g.bounds.y = rs8(ifs); - g.bounds.w = ru8(ifs); - g.bounds.h = ru8(ifs); - g.advance = ru8(ifs); - - g.contours.paths = (pp_path_t *)malloc(sizeof(pp_path_t) * 10); - - if(ifs.fail()) { - // could not read glyph dictionary entry - return false; - } - - // allocate space for the contour data and read it from the font file - uint16_t contour_data_length = ru16(ifs); - - // remember where we are in the dictionary - int pos = ifs.tell(); - - // read contour data - ifs.seek(contour_data_offset); - while(true) { - // get number of points in contour - uint16_t count = ru16(ifs); - - // if count is zero then this is the end of contour marker - if(count == 0) { - break; - } - - // allocate space to store point data for contour and read - // from file - g.contours.paths[g.contours.count].points = (pp_point_t *)malloc(sizeof(pp_point_t) * count); - - ifs.read((char *)g.contours.paths[g.contours.count].points, sizeof(pp_point_t) * count); - - g.contours.count ++; - } - - // return back to position in dictionary - ifs.seek(pos); - contour_data_offset += contour_data_length; - - if(ifs.fail()) { - // could not read glyph contour data - return false; - } - - this->glyphs[g.codepoint] = g; - } - - return true; - } - - bool face_t::load(std::string_view path) { - file_io ifs(path); - if(ifs.fail()) { - // could not open file - return false; - } - return load(ifs); - } - -} \ No newline at end of file diff --git a/libraries/pico_vector/alright_fonts.hpp b/libraries/pico_vector/alright_fonts.hpp deleted file mode 100644 index 2bd83b65e..000000000 --- a/libraries/pico_vector/alright_fonts.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "pretty-poly.h" -#include "file_io.hpp" - -namespace alright_fonts { - - struct glyph_t { - uint16_t codepoint; - pp_rect_t bounds; - uint8_t advance; - pp_poly_t contours; - }; - - struct face_t { - uint16_t glyph_count; - uint16_t flags; - std::map glyphs; - - face_t() {}; - face_t(file_io &ifs) {load(ifs);} - face_t(std::string_view path) {load(path);} - - bool load(file_io &ifs); - bool load(std::string_view path); - }; - - enum alignment_t { - left = 0, - center = 1, - right = 2, - justify = 4, - top = 8, - bottom = 16 - }; - - struct text_metrics_t { - face_t face; // font to write in - int size; // text size in pixels - uint scroll; // vertical scroll offset - int line_height; // spacing between lines (%) - int letter_spacing; // spacing between characters - int word_spacing; // spacing between words - alignment_t align; // horizontal and vertical alignment - //optional transform; // arbitrary transformation - pp_antialias_t antialiasing = PP_AA_X4; // level of antialiasing to apply - - void set_size(int s) { - size = s; - line_height = size; - letter_spacing = 0; - word_spacing = size / 2; - } - - text_metrics_t() {}; - }; - - /* - utility functions - */ - pp_rect_t measure_character(text_metrics_t &tm, uint16_t codepoint); - - /* - render functions - */ - - void render_character(text_metrics_t &tm, uint16_t codepoint, pp_point_t origin, pp_mat3_t *transform); -} \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.cmake b/libraries/pico_vector/pico_vector.cmake index 73f17c5f1..3a3af2d5e 100644 --- a/libraries/pico_vector/pico_vector.cmake +++ b/libraries/pico_vector/pico_vector.cmake @@ -4,7 +4,8 @@ endif() add_library(pico_vector ${CMAKE_CURRENT_LIST_DIR}/pico_vector.cpp - ${CMAKE_CURRENT_LIST_DIR}/alright_fonts.cpp + ${CMAKE_CURRENT_LIST_DIR}/af-file-io.c + ${CMAKE_CURRENT_LIST_DIR}/af-memory.c ) target_include_directories(pico_vector INTERFACE ${CMAKE_CURRENT_LIST_DIR}) diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index 1d96aabdf..d76904d53 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -112,7 +112,20 @@ namespace pimoroni { return result; } - pp_point_t text(std::string_view text, pp_mat3_t *t); + bool set_font(void* font, unsigned int font_size) { + 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 = af_load_font_file(font, text_metrics.face); + + set_font_size(font_size); + + return result; + } + + pp_point_t text(std::string_view text, pp_mat3_t *t=nullptr); void transform(pp_path_t *path, pp_mat3_t *t); void transform(pp_poly_t *poly, pp_mat3_t *t); From 94d14ed97005ccca682e39b835062ee7fc02ccf1 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 9 Jul 2024 14:59:01 +0100 Subject: [PATCH 14/35] PicoVector: Remove alright_fonts.cpp from cmake. --- micropython/modules/picovector/micropython.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/micropython/modules/picovector/micropython.cmake b/micropython/modules/picovector/micropython.cmake index 381d8b50c..f5f65cea9 100644 --- a/micropython/modules/picovector/micropython.cmake +++ b/micropython/modules/picovector/micropython.cmake @@ -2,7 +2,6 @@ add_library(usermod_picovector INTERFACE) target_sources(usermod_picovector INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_vector/pico_vector.cpp - ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_vector/alright_fonts.cpp ${CMAKE_CURRENT_LIST_DIR}/picovector.c ${CMAKE_CURRENT_LIST_DIR}/picovector.cpp ) From 031956a0997ebc29939476cfe9b0e40be6f1af2f Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 11 Jul 2024 09:40:37 +0100 Subject: [PATCH 15/35] PicoGraphics: Add RGB565 alpha blending support. --- libraries/pico_graphics/pico_graphics.hpp | 3 +++ libraries/pico_graphics/pico_graphics_pen_rgb565.cpp | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/libraries/pico_graphics/pico_graphics.hpp b/libraries/pico_graphics/pico_graphics.hpp index c16f58f35..677d76955 100644 --- a/libraries/pico_graphics/pico_graphics.hpp +++ b/libraries/pico_graphics/pico_graphics.hpp @@ -532,6 +532,9 @@ namespace pimoroni { } void frame_convert(PenType type, conversion_callback_func callback) override; + void set_pixel_alpha(const Point &p, const uint8_t a) override; + + bool supports_alpha_blend() override {return true;} }; class PicoGraphics_PenRGB888 : public PicoGraphics { diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp index b2e21bd7c..6c5b3910c 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp @@ -62,6 +62,16 @@ namespace pimoroni { } } + void PicoGraphics_PenRGB565::set_pixel_alpha(const Point &p, const uint8_t a) { + if(!bounds.contains(p)) return; + + uint16_t *buf = (uint16_t *)frame_buffer; + + RGB565 blended = RGB(buf[p.y * bounds.w + p.x]).blend(RGB(color), a).to_rgb565(); + + buf[p.y * bounds.w + p.x] = blended; + }; + void PicoGraphics_PenRGB565::sprite(void* data, const Point &sprite, const Point &dest, const int scale, const int transparent) { //int sprite_x = (sprite & 0x0f) << 3; //int sprite_y = (sprite & 0xf0) >> 1; From dcc24fdceba94457dc33dfbeb2addb1f3bd3403d Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 11 Jul 2024 09:41:29 +0100 Subject: [PATCH 16/35] PicoVector: Fix x16 anti-aliasing. --- libraries/pico_vector/pico_vector.cpp | 3 +++ libraries/pico_vector/pico_vector.hpp | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp index 9c20f0735..933ef2a17 100644 --- a/libraries/pico_vector/pico_vector.cpp +++ b/libraries/pico_vector/pico_vector.cpp @@ -6,6 +6,9 @@ namespace pimoroni { PicoGraphics *PicoVector::graphics = nullptr; + uint8_t PicoVector::max_alpha = 4; + const uint8_t *PicoVector::alpha_map = alpha_map_x4; + void PicoVector::draw(pp_poly_t *poly) { pp_transform(NULL); pp_render(poly); diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index d76904d53..534fe3bda 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -31,7 +31,10 @@ namespace pimoroni { private: static PicoGraphics *graphics; af_text_metrics_t text_metrics; - static constexpr uint8_t alpha_map[4] {0, 128, 192, 255}; + static constexpr uint8_t alpha_map_x4[4] {0, 128, 192, 255}; + static constexpr uint8_t alpha_map_x16[16] {0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 255}; + static uint8_t max_alpha; + static const uint8_t *alpha_map; public: PicoVector(PicoGraphics *graphics, void *mem = nullptr) { @@ -39,7 +42,7 @@ namespace pimoroni { pp_tile_callback(PicoVector::tile_callback); - pp_antialias(graphics->supports_alpha_blend() ? PP_AA_X4 : PP_AA_NONE); + set_antialiasing(graphics->supports_alpha_blend() ? PP_AA_X4 : PP_AA_NONE); pp_clip(graphics->clip.x, graphics->clip.y, graphics->clip.w, graphics->clip.h); @@ -67,7 +70,7 @@ namespace pimoroni { for(auto y = 0; y < tile->h; y++) { for(auto x = 0; x < tile->w; x++) { uint8_t alpha = *tile_data++; - if (alpha >= 4) { + if (alpha >= max_alpha) { PicoVector::graphics->pixel({x + tile->x, y + tile->y}); } else if (alpha > 0) { alpha = alpha_map[alpha]; @@ -91,6 +94,13 @@ namespace pimoroni { void set_antialiasing(pp_antialias_t antialias) { pp_antialias(antialias); + if(antialias == PP_AA_X16) { + alpha_map = alpha_map_x16; + max_alpha = 16; + } else { + alpha_map = alpha_map_x4; + max_alpha = 4; + } } void set_font_size(unsigned int font_size) { From 263fe406c36c337050dc20f54664ce535d47b15b Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 11 Jul 2024 09:43:01 +0100 Subject: [PATCH 17/35] PicoGraphics: Add get_clip. --- micropython/modules/picographics/picographics.c | 2 ++ micropython/modules/picographics/picographics.cpp | 12 ++++++++++++ micropython/modules/picographics/picographics.h | 1 + 3 files changed, 15 insertions(+) diff --git a/micropython/modules/picographics/picographics.c b/micropython/modules/picographics/picographics.c index ad6d0dc57..1a504c1ea 100644 --- a/micropython/modules/picographics/picographics.c +++ b/micropython/modules/picographics/picographics.c @@ -29,6 +29,7 @@ MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_layer_obj, ModPicoGraphics_set_lay // Primitives MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_set_clip_obj, 5, 5, ModPicoGraphics_set_clip); +MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics_get_clip_obj, ModPicoGraphics_get_clip); MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics_remove_clip_obj, ModPicoGraphics_remove_clip); MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics_clear_obj, ModPicoGraphics_clear); MP_DEFINE_CONST_FUN_OBJ_3(ModPicoGraphics_pixel_obj, ModPicoGraphics_pixel); @@ -69,6 +70,7 @@ static const mp_rom_map_elem_t ModPicoGraphics_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_partial_update), MP_ROM_PTR(&ModPicoGraphics_partial_update_obj) }, { MP_ROM_QSTR(MP_QSTR_set_update_speed), MP_ROM_PTR(&ModPicoGraphics_set_update_speed_obj) }, { MP_ROM_QSTR(MP_QSTR_set_clip), MP_ROM_PTR(&ModPicoGraphics_set_clip_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_clip), MP_ROM_PTR(&ModPicoGraphics_get_clip_obj) }, { MP_ROM_QSTR(MP_QSTR_remove_clip), MP_ROM_PTR(&ModPicoGraphics_remove_clip_obj) }, { MP_ROM_QSTR(MP_QSTR_pixel_span), MP_ROM_PTR(&ModPicoGraphics_pixel_span_obj) }, { MP_ROM_QSTR(MP_QSTR_rectangle), MP_ROM_PTR(&ModPicoGraphics_rectangle_obj) }, diff --git a/micropython/modules/picographics/picographics.cpp b/micropython/modules/picographics/picographics.cpp index 8ac300cf0..218c95788 100644 --- a/micropython/modules/picographics/picographics.cpp +++ b/micropython/modules/picographics/picographics.cpp @@ -889,6 +889,18 @@ mp_obj_t ModPicoGraphics_set_clip(size_t n_args, const mp_obj_t *args) { return mp_const_none; } +mp_obj_t ModPicoGraphics_get_clip(mp_obj_t self_in) { + ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t); + + mp_obj_t tuple[4] = { + mp_obj_new_int(self->graphics->clip.x), + mp_obj_new_int(self->graphics->clip.y), + mp_obj_new_int(self->graphics->clip.w), + mp_obj_new_int(self->graphics->clip.h) + }; + return mp_obj_new_tuple(4, tuple); +} + mp_obj_t ModPicoGraphics_remove_clip(mp_obj_t self_in) { ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t); diff --git a/micropython/modules/picographics/picographics.h b/micropython/modules/picographics/picographics.h index 1238354d9..6457599ab 100644 --- a/micropython/modules/picographics/picographics.h +++ b/micropython/modules/picographics/picographics.h @@ -88,6 +88,7 @@ extern mp_obj_t ModPicoGraphics_set_thickness(mp_obj_t self_in, mp_obj_t thickne // Primitives extern mp_obj_t ModPicoGraphics_set_clip(size_t n_args, const mp_obj_t *args); +extern mp_obj_t ModPicoGraphics_get_clip(mp_obj_t self_in); extern mp_obj_t ModPicoGraphics_remove_clip(mp_obj_t self_in); extern mp_obj_t ModPicoGraphics_clear(mp_obj_t self_in); extern mp_obj_t ModPicoGraphics_pixel(mp_obj_t self_in, mp_obj_t x, mp_obj_t y); From 7d04ea78a0826e899cb8e746505a33ff99ce0335 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 11 Jul 2024 09:44:12 +0100 Subject: [PATCH 18/35] PicoVector: Remove malloc from MicroPython bindings. --- micropython/modules/picovector/picovector.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index 08daca658..eefe2d9ca 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -520,7 +520,7 @@ mp_obj_t VECTOR_draw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) pp_poly_t group; group.count = num_polygons; - group.paths = (pp_path_t *)malloc(sizeof(pp_path_t) * num_polygons); + group.paths = (pp_path_t *)m_new(pp_path_t, num_polygons); for(auto i = 0u; i < num_polygons; i++) { mp_obj_t poly_obj = polygons[i]; @@ -534,7 +534,7 @@ mp_obj_t VECTOR_draw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) self->vector->draw(&group); - free(group.paths); + m_free(group.paths); return mp_const_none; } From 740e3514938caad115a3e3087acf4e50779c7d81 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 11 Jul 2024 09:45:53 +0100 Subject: [PATCH 19/35] PicoVector: Support float types in MicroPython bindings. --- micropython/modules/picovector/picovector.cpp | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index eefe2d9ca..5cba3cea6 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -37,6 +37,9 @@ void __printf_debug_flush() { } } +#define mp_picovector_get_point_type mp_obj_get_float +#define mp_picovector_set_point_type mp_obj_new_float + int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args); void af_debug(const char *fmt, ...) { @@ -157,10 +160,10 @@ static const std::string_view mp_obj_to_string_r(const mp_obj_t &obj) { mp_obj_t RECTANGLE_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_x, ARG_y, ARG_w, ARG_h }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_INT }, - { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_INT }, - { MP_QSTR_w, MP_ARG_REQUIRED | MP_ARG_INT }, - { MP_QSTR_h, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_w, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_h, MP_ARG_REQUIRED | MP_ARG_OBJ }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -168,10 +171,10 @@ mp_obj_t RECTANGLE_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_k _PATH_obj_t *self = mp_obj_malloc_with_finaliser(_PATH_obj_t, &POLYGON_type); - int x = args[ARG_x].u_int; - int y = args[ARG_y].u_int; - int w = args[ARG_w].u_int; - int h = args[ARG_h].u_int; + picovector_point_type x = mp_picovector_get_point_type(args[ARG_x].u_obj); + picovector_point_type y = mp_picovector_get_point_type(args[ARG_y].u_obj); + picovector_point_type w = mp_picovector_get_point_type(args[ARG_w].u_obj); + picovector_point_type h = mp_picovector_get_point_type(args[ARG_h].u_obj); self->path.points = m_new(pp_point_t, 4); self->path.count = 4; @@ -187,8 +190,8 @@ mp_obj_t RECTANGLE_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_k mp_obj_t REGULAR_POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_x, ARG_y, ARG_sides, ARG_radius, ARG_rotation }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_INT }, - { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_sides, MP_ARG_REQUIRED | MP_ARG_INT }, { MP_QSTR_radius, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_rotation, MP_ARG_OBJ, {.u_obj = mp_const_none} }, @@ -207,8 +210,8 @@ mp_obj_t REGULAR_POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size rotation = mp_obj_get_float(args[ARG_rotation].u_obj); rotation *= (M_PI / 180.0f); } - int o_x = args[ARG_x].u_int; - int o_y = args[ARG_y].u_int; + picovector_point_type o_x = mp_picovector_get_point_type(args[ARG_x].u_obj); + picovector_point_type o_y = mp_picovector_get_point_type(args[ARG_y].u_obj); float angle = (360.0f / sides) * (M_PI / 180.0f); @@ -247,8 +250,8 @@ mp_obj_t POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, if(t_point->len != 2) mp_raise_ValueError("Tuple must have X, Y"); self->path.points[i] = { - (picovector_point_type)mp_obj_get_int(t_point->items[0]), - (picovector_point_type)mp_obj_get_int(t_point->items[1]), + (picovector_point_type)mp_picovector_get_point_type(t_point->items[0]), + (picovector_point_type)mp_picovector_get_point_type(t_point->items[1]), }; } @@ -270,8 +273,8 @@ mp_obj_t POLYGON_centroid(mp_obj_t self_in) { sum_y /= (float)self->path.count; mp_obj_t tuple[2]; - tuple[0] = mp_obj_new_int((int)(sum_x)); - tuple[1] = mp_obj_new_int((int)(sum_y)); + tuple[0] = mp_picovector_set_point_type((int)(sum_x)); + tuple[1] = mp_picovector_set_point_type((int)(sum_y)); return mp_obj_new_tuple(2, tuple); } @@ -282,10 +285,10 @@ mp_obj_t POLYGON_bounds(mp_obj_t self_in) { pp_rect_t bounds = pp_contour_bounds(&self->path); mp_obj_t tuple[4]; - tuple[0] = mp_obj_new_int((int)(bounds.x)); - tuple[1] = mp_obj_new_int((int)(bounds.y)); - tuple[2] = mp_obj_new_int((int)(bounds.w)); - tuple[3] = mp_obj_new_int((int)(bounds.h)); + tuple[0] = mp_picovector_set_point_type((int)(bounds.x)); + tuple[1] = mp_picovector_set_point_type((int)(bounds.y)); + tuple[2] = mp_picovector_set_point_type((int)(bounds.w)); + tuple[3] = mp_picovector_set_point_type((int)(bounds.h)); return mp_obj_new_tuple(4, tuple); } @@ -297,15 +300,15 @@ void POLYGON_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t ki pp_rect_t bounds = pp_contour_bounds(&self->path); mp_print_str(print, "Polygon(points = "); - mp_obj_print_helper(print, mp_obj_new_int(self->path.count), PRINT_REPR); + mp_obj_print_helper(print, mp_picovector_set_point_type(self->path.count), PRINT_REPR); mp_print_str(print, ", bounds = "); - mp_obj_print_helper(print, mp_obj_new_int(bounds.x), PRINT_REPR); + mp_obj_print_helper(print, mp_picovector_set_point_type(bounds.x), PRINT_REPR); mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_obj_new_int(bounds.y), PRINT_REPR); + mp_obj_print_helper(print, mp_picovector_set_point_type(bounds.y), PRINT_REPR); mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_obj_new_int(bounds.w), PRINT_REPR); + mp_obj_print_helper(print, mp_picovector_set_point_type(bounds.w), PRINT_REPR); mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_obj_new_int(bounds.h), PRINT_REPR); + mp_obj_print_helper(print, mp_picovector_set_point_type(bounds.h), PRINT_REPR); mp_print_str(print, ")"); } @@ -332,8 +335,8 @@ static mp_obj_t py_path_it_iternext(mp_obj_t self_in) { if(self->cur >= path->path.count) return MP_OBJ_STOP_ITERATION; mp_obj_t tuple[2]; - tuple[0] = mp_obj_new_int((int)(path->path.points[self->cur].x)); - tuple[1] = mp_obj_new_int((int)(path->path.points[self->cur].y)); + tuple[0] = mp_picovector_set_point_type((int)(path->path.points[self->cur].x)); + tuple[1] = mp_picovector_set_point_type((int)(path->path.points[self->cur].y)); self->cur++; return mp_obj_new_tuple(2, tuple); From be0d6179421b5c1c8c95d9d8f2d857a640c9bfd6 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 22 Jul 2024 15:32:33 +0100 Subject: [PATCH 20/35] PicoVector: Use tile renderer for all pens. --- libraries/pico_graphics/pico_graphics.hpp | 18 +- .../pico_graphics/pico_graphics_pen_p4.cpp | 23 ++ .../pico_graphics/pico_graphics_pen_p8.cpp | 20 ++ .../pico_graphics_pen_rgb332.cpp | 20 ++ .../pico_graphics_pen_rgb565.cpp | 37 +++ .../pico_graphics_pen_rgb888.cpp | 20 ++ libraries/pico_vector/pico_vector.cpp | 3 - libraries/pico_vector/pico_vector.hpp | 47 +-- libraries/pico_vector/pretty-poly.h | 285 ++++++++---------- micropython/modules/picovector/picovector.c | 2 + 10 files changed, 262 insertions(+), 213 deletions(-) diff --git a/libraries/pico_graphics/pico_graphics.hpp b/libraries/pico_graphics/pico_graphics.hpp index 677d76955..9953f7549 100644 --- a/libraries/pico_graphics/pico_graphics.hpp +++ b/libraries/pico_graphics/pico_graphics.hpp @@ -143,6 +143,12 @@ namespace pimoroni { typedef int Pen; + struct Tile { + int32_t x, y, w, h; + uint32_t stride; + uint8_t *data; + }; + struct Rect; struct Point { @@ -307,7 +313,7 @@ namespace pimoroni { virtual void frame_convert(PenType type, conversion_callback_func callback); virtual void sprite(void* data, const Point &sprite, const Point &dest, const int scale, const int transparent); - virtual bool render_pico_vector_tile(const Rect &bounds, uint8_t* alpha_data, uint32_t stride, uint8_t alpha_type) { return false; } + virtual bool render_tile(const Tile *tile) { return false; } void set_font(const bitmap::font_t *font); void set_font(const hershey::font_t *font); @@ -454,6 +460,8 @@ namespace pimoroni { static size_t buffer_size(uint w, uint h) { return w * h / 2; } + + bool render_tile(const Tile *tile); }; class PicoGraphics_PenP8 : public PicoGraphics { @@ -487,6 +495,8 @@ namespace pimoroni { static size_t buffer_size(uint w, uint h) { return w * h; } + + bool render_tile(const Tile *tile); }; class PicoGraphics_PenRGB332 : public PicoGraphics { @@ -511,6 +521,8 @@ namespace pimoroni { static size_t buffer_size(uint w, uint h) { return w * h; } + + bool render_tile(const Tile *tile); }; class PicoGraphics_PenRGB565 : public PicoGraphics { @@ -535,6 +547,8 @@ namespace pimoroni { void set_pixel_alpha(const Point &p, const uint8_t a) override; bool supports_alpha_blend() override {return true;} + + bool render_tile(const Tile *tile); }; class PicoGraphics_PenRGB888 : public PicoGraphics { @@ -551,6 +565,8 @@ namespace pimoroni { static size_t buffer_size(uint w, uint h) { return w * h * sizeof(uint32_t); } + + bool render_tile(const Tile *tile); }; diff --git a/libraries/pico_graphics/pico_graphics_pen_p4.cpp b/libraries/pico_graphics/pico_graphics_pen_p4.cpp index bf5edc3fc..dcb5fe19d 100644 --- a/libraries/pico_graphics/pico_graphics_pen_p4.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_p4.cpp @@ -181,4 +181,27 @@ namespace pimoroni { } } } + bool PicoGraphics_PenP4::render_tile(const Tile *tile) { + for(int y = 0; y < tile->h; y++) { + uint8_t *palpha = &tile->data[(y * tile->stride)]; + uint8_t *pdest = &((uint8_t *)frame_buffer)[(tile->x / 2) + ((tile->y + y) * (bounds.w / 2))]; + for(int x = 0; x < tile->w; x++) { + uint8_t shift = (x & 1) ? 0 : 4; + uint8_t alpha = *palpha; + + if(alpha == 0) { + } else { + *pdest &= shift ? 0x0f : 0xf0; + *pdest |= color << shift; + } + + if(x & 1) { + pdest++; + } + palpha++; + } + } + + return true; + } } diff --git a/libraries/pico_graphics/pico_graphics_pen_p8.cpp b/libraries/pico_graphics/pico_graphics_pen_p8.cpp index fd952690f..10f320981 100644 --- a/libraries/pico_graphics/pico_graphics_pen_p8.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_p8.cpp @@ -171,4 +171,24 @@ namespace pimoroni { } } } + + bool PicoGraphics_PenP8::render_tile(const Tile *tile) { + for(int y = 0; y < tile->h; y++) { + uint8_t *palpha = &tile->data[(y * tile->stride)]; + uint8_t *pdest = &((uint8_t *)frame_buffer)[tile->x + ((tile->y + y) * bounds.w)]; + for(int x = 0; x < tile->w; x++) { + uint8_t alpha = *palpha; + + if(alpha == 0) { + } else { + *pdest = color; + } + + pdest++; + palpha++; + } + } + + return true; + } } diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp index 3c8ca8521..cdb24da2c 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp @@ -145,4 +145,24 @@ namespace pimoroni { } } } + bool PicoGraphics_PenRGB332::render_tile(const Tile *tile) { + for(int y = 0; y < tile->h; y++) { + uint8_t *palpha = &tile->data[(y * tile->stride)]; + uint8_t *pdest = &((uint8_t *)frame_buffer)[tile->x + ((tile->y + y) * bounds.w)]; + for(int x = 0; x < tile->w; x++) { + uint8_t alpha = *palpha; + + // TODO: Try to alpha blend RGB332... somewhat? + if(alpha == 0) { + } else { + *pdest = color; + } + + pdest++; + palpha++; + } + } + + return true; + } } diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp index 6c5b3910c..6cf2d8638 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp @@ -93,4 +93,41 @@ namespace pimoroni { } } } + + bool PicoGraphics_PenRGB565::render_tile(const Tile *tile) { + for(int y = 0; y < tile->h; y++) { + uint8_t *palpha = &tile->data[(y * tile->stride)]; + uint16_t *pdest = &((uint16_t *)frame_buffer)[tile->x + ((tile->y + y) * bounds.w)]; + for(int x = 0; x < tile->w; x++) { + uint16_t dest = *pdest; + uint8_t alpha = *palpha; + + if(alpha == 255) { + *pdest = color; + }else if(alpha == 0) { + }else{ + // blend tha pixel + uint16_t sr = (color & 0b1111100000000000) >> 11; + uint16_t sg = (color & 0b0000011111100000) >> 5; + uint16_t sb = (color & 0b0000000000011111); + + uint16_t dr = (dest & 0b1111100000000000) >> 11; + uint16_t dg = (dest & 0b0000011111100000) >> 5; + uint16_t db = (dest & 0b0000000000011111); + + uint8_t r = ((sr * alpha) + (dr * (255 - alpha))) >> 8; + uint8_t g = ((sg * alpha) + (dg * (255 - alpha))) >> 8; + uint8_t b = ((sb * alpha) + (db * (255 - alpha))) >> 8; + + // recombine the channels + *pdest = (r << 11) | (g << 5) | (b); + } + + pdest++; + palpha++; + } + } + + return true; + } } diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp index 0b145239b..642557a46 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp @@ -36,4 +36,24 @@ namespace pimoroni { *buf++ = color; } } + bool PicoGraphics_PenRGB888::render_tile(const Tile *tile) { + for(int y = 0; y < tile->h; y++) { + uint8_t *palpha = &tile->data[(y * tile->stride)]; + uint32_t *pdest = &((uint32_t *)frame_buffer)[tile->x + ((tile->y + y) * bounds.w)]; + for(int x = 0; x < tile->w; x++) { + uint8_t alpha = *palpha; + + // TODO: Alpha blending + if(alpha == 0) { + } else { + *pdest = color; + } + + pdest++; + palpha++; + } + } + + return true; + } } \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp index 933ef2a17..9c20f0735 100644 --- a/libraries/pico_vector/pico_vector.cpp +++ b/libraries/pico_vector/pico_vector.cpp @@ -6,9 +6,6 @@ namespace pimoroni { PicoGraphics *PicoVector::graphics = nullptr; - uint8_t PicoVector::max_alpha = 4; - const uint8_t *PicoVector::alpha_map = alpha_map_x4; - void PicoVector::draw(pp_poly_t *poly) { pp_transform(NULL); pp_render(poly); diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index 534fe3bda..1bcaf3406 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -31,10 +31,6 @@ namespace pimoroni { private: static PicoGraphics *graphics; af_text_metrics_t text_metrics; - static constexpr uint8_t alpha_map_x4[4] {0, 128, 192, 255}; - static constexpr uint8_t alpha_map_x16[16] {0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 255}; - static uint8_t max_alpha; - static const uint8_t *alpha_map; public: PicoVector(PicoGraphics *graphics, void *mem = nullptr) { @@ -57,50 +53,13 @@ namespace pimoroni { } static void tile_callback(const pp_tile_t *tile) { - uint8_t *tile_data = tile->data; - - if(PicoVector::graphics->supports_alpha_blend() && _pp_antialias != PP_AA_NONE) { - if (PicoVector::graphics->render_pico_vector_tile({tile->x, tile->y, tile->w, tile->h}, - tile->data, - tile->stride, - (uint8_t)_pp_antialias)) { - return; - } - - for(auto y = 0; y < tile->h; y++) { - for(auto x = 0; x < tile->w; x++) { - uint8_t alpha = *tile_data++; - if (alpha >= max_alpha) { - PicoVector::graphics->pixel({x + tile->x, y + tile->y}); - } else if (alpha > 0) { - alpha = alpha_map[alpha]; - PicoVector::graphics->set_pixel_alpha({x + tile->x, y + tile->y}, alpha); - } - } - tile_data += tile->stride - tile->w; - } - } else { - for(auto y = 0; y < tile->h; y++) { - for(auto x = 0; x < tile->w; x++) { - uint8_t alpha = *tile_data++; - if (alpha) { - PicoVector::graphics->pixel({x + tile->x, y + tile->y}); - } - } - tile_data += tile->stride - tile->w; - } - } + // TODO: we're using a cast here to avoid a hard dependency link between + // PicoGraphics and PicoVector. These types might subtly mismatch, though... + PicoVector::graphics->render_tile((pimoroni::Tile *)tile); } void set_antialiasing(pp_antialias_t antialias) { pp_antialias(antialias); - if(antialias == PP_AA_X16) { - alpha_map = alpha_map_x16; - max_alpha = 16; - } else { - alpha_map = alpha_map_x4; - max_alpha = 4; - } } void set_font_size(unsigned int font_size) { diff --git a/libraries/pico_vector/pretty-poly.h b/libraries/pico_vector/pretty-poly.h index 0bd554556..c7a7c10ec 100644 --- a/libraries/pico_vector/pretty-poly.h +++ b/libraries/pico_vector/pretty-poly.h @@ -31,30 +31,25 @@ #include #include +#include #include #include #include -#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 +#define PP_TILE_BUFFER_SIZE 64 +#endif + +#ifndef PP_SCALE_TO_ALPHA +#define PP_SCALE_TO_ALPHA 1 #endif #if defined(PICO_ON_DEVICE) && PICO_ON_DEVICE @@ -62,12 +57,6 @@ #include "hardware/interp.h" #endif -#ifdef PP_DEBUG -#define debug(...) printf(__VA_ARGS__) -#else -#define debug(...) -#endif - #ifdef __cplusplus extern "C" { #endif @@ -103,7 +92,7 @@ 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 enum {PP_AA_NONE = 0, PP_AA_FAST = 1, PP_AA_X4 = 1, PP_AA_BEST = 2, PP_AA_X16 = 2} pp_antialias_t; typedef struct { int32_t x, y, w, h; @@ -144,7 +133,19 @@ pp_rect_t pp_polygon_bounds(pp_poly_t *p); #ifdef PP_IMPLEMENTATION -pp_rect_t _pp_clip = (pp_rect_t){0, 0, 320, 240}; +#ifndef PP_MALLOC +#define PP_MALLOC(size) malloc(size) +#define PP_REALLOC(p, size) realloc(p, size) +#define PP_FREE(p) free(p) +#endif + +#ifdef PP_DEBUG +#define debug(...) printf(__VA_ARGS__) +#else +#define debug(...) +#endif + +pp_rect_t _pp_clip = (pp_rect_t){-INT_MAX, -INT_MAX, INT_MAX, INT_MAX}; pp_tile_callback_t _pp_tile_callback = NULL; pp_antialias_t _pp_antialias = PP_AA_X4; pp_mat3_t *_pp_transform = NULL; @@ -245,7 +246,7 @@ pp_rect_t pp_rect_transform(pp_rect_t *r, pp_mat3_t *m) { // 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); + return tile->data[(x - tile->x) + (y - tile->y) * PP_TILE_BUFFER_SIZE]; } // pp_contour_t implementation @@ -273,14 +274,16 @@ pp_rect_t pp_polygon_bounds(pp_poly_t *p) { // 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]; +uint8_t tile_buffer[PP_TILE_BUFFER_SIZE * PP_TILE_BUFFER_SIZE]; // 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]; +int32_t nodes[PP_TILE_BUFFER_SIZE * 4][PP_MAX_NODES_PER_SCANLINE * 2]; +uint32_t node_counts[PP_TILE_BUFFER_SIZE * 4]; +uint8_t _pp_alpha_map_none[2] = {0, 255}; +uint8_t _pp_alpha_map_x4[5] = {0, 63, 127, 190, 255}; +uint8_t _pp_alpha_map_x16[17] = {0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 255}; 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}; @@ -291,12 +294,8 @@ void pp_tile_callback(pp_tile_callback_t 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) { @@ -329,34 +328,15 @@ void add_line_segment_to_nodes(const pp_point_t start, const pp_point_t end) { 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; + // early out if line is completely outside the tile, or has no gradient + if (ey < 0 || sy >= (int)(PP_TILE_BUFFER_SIZE << _pp_antialias) || 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 + // 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; + int count = _pp_min((int)(PP_TILE_BUFFER_SIZE << _pp_antialias), 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; @@ -364,7 +344,7 @@ void add_line_segment_to_nodes(const pp_point_t start, const pp_point_t end) { 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 + // 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; @@ -373,34 +353,34 @@ void add_line_segment_to_nodes(const pp_point_t start, const pp_point_t end) { 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 +// #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); + int nx = _pp_max(_pp_min(x, (PP_TILE_BUFFER_SIZE << _pp_antialias)), 0); + //debug(" + adding node at %d, %d\n", x, y); // add node to node list nodes[y][node_counts[y]++] = nx; @@ -408,7 +388,7 @@ void add_line_segment_to_nodes(const pp_point_t start, const pp_point_t end) { y++; e += einc; } -#endif +//#endif } void build_nodes(pp_path_t *contour, pp_rect_t *bounds) { @@ -419,34 +399,17 @@ void build_nodes(pp_path_t *contour, pp_rect_t *bounds) { .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; - + // start with the last point to close the loop, transform it, scale for antialiasing, and offset to tile origin + pp_point_t last = contour->points[contour->count - 1]; + 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; - + // fetch next point, transform it, scale for antialiasing, and offset to tile origin + pp_point_t point = contour->points[i]; + 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); @@ -459,81 +422,75 @@ 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 render_nodes(pp_rect_t *tb) { + pp_rect_t rb = {PP_TILE_BUFFER_SIZE << _pp_antialias, PP_TILE_BUFFER_SIZE << _pp_antialias, 0, 0}; // render bounds + int maxx = 0, minx = PP_TILE_BUFFER_SIZE << _pp_antialias; + debug(" + render tile %d, %d - %d, %d\n", tb->x, tb->y, tb->w, tb->h); - 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(int y = 0; y < ((int)PP_TILE_BUFFER_SIZE << _pp_antialias); y++) { - for(int32_t y = 0; y < PP_NODE_BUFFER_HEIGHT; y++) { - if(node_counts[y] == 0) { - if (y == rb.y) ++rb.y; - continue; - } + // debug(" : row %d node count %d\n", y, node_counts[y]); + + if(node_counts[y] == 0) continue; // no nodes on this raster line 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; + unsigned char* row_data = &tile_buffer[(y >> _pp_antialias) * PP_TILE_BUFFER_SIZE]; + 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) { + if(sx == ex) { // empty span, nothing to do continue; } - rendered_any = true; - - maxx = _pp_max((ex - 1) >> _pp_antialias, maxx); + // update render bounds + rb.x = _pp_min(rb.x, sx); + rb.y = _pp_min(rb.y, y); + minx = _pp_min(_pp_min(sx, ex), minx); + maxx = _pp_max(_pp_max(sx, ex), maxx); + rb.h = y - rb.y + 1; - debug(" - render span at %d from %d to %d\n", y, sx, ex); + //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); + // rasterise the span into the tile buffer + do { + row_data[sx >> _pp_antialias]++; + } while(++sx < ex); + } + } - if (ax == aex) { - row_data[ax] += ex - sx; - continue; - } + rb.w = maxx - minx; - row_data[ax] += aa_scale - (sx & anitialias_mask); - for(ax++; ax < aex; ax++) { - row_data[ax] += aa_scale; - } + // shifting the width and height effectively "floors" the result which can + // mean we lose a pixel off the right or bottom edge of the tile. by adding + // either 1 (at x4) or 3 (at x16) we change that to a "ceil" instead ensuring + // the full tile bounds are returned + if(_pp_antialias) { + rb.w += (_pp_antialias | 0b1); + rb.h += (_pp_antialias | 0b1); + } - // 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]++; - } + rb.x >>= _pp_antialias; + rb.y >>= _pp_antialias; + rb.w >>= _pp_antialias; + rb.h >>= _pp_antialias; + + uint8_t *p_alpha_map = _pp_alpha_map_none; + if(_pp_antialias == 1) p_alpha_map = _pp_alpha_map_x4; + if(_pp_antialias == 2) p_alpha_map = _pp_alpha_map_x16; + #if PP_SCALE_TO_ALPHA == 1 + for(int y = rb.y; y < rb.y + rb.h; y++) { + unsigned char* row_data = &tile_buffer[y * PP_TILE_BUFFER_SIZE + rb.x]; + for(int x = rb.x; x < rb.x + rb.w; x++) { + *row_data = p_alpha_map[*row_data]; + row_data++; } } + #endif - 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; + debug(" : rendered tile bounds %d, %d (%d x %d)\n", rb.x, rb.y, rb.w, rb.h); return rb; } @@ -553,7 +510,7 @@ void pp_render(pp_poly_t *polygon) { 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(" - polygon 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 @@ -569,9 +526,9 @@ void pp_render(pp_poly_t *polygon) { // 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}; + for(int32_t y = polygon_bounds.y; y < polygon_bounds.y + polygon_bounds.h; y += PP_TILE_BUFFER_SIZE) { + for(int32_t x = polygon_bounds.x; x < polygon_bounds.x + polygon_bounds.w; x += PP_TILE_BUFFER_SIZE) { + pp_rect_t tb = (pp_rect_t){.x = x, .y = y, .w = PP_TILE_BUFFER_SIZE, .h = PP_TILE_BUFFER_SIZE}; tb = pp_rect_intersection(&tb, &_pp_clip); debug(" : %d, %d (%d x %d)\n", tb.x, tb.y, tb.w, tb.h); @@ -580,7 +537,7 @@ void pp_render(pp_poly_t *polygon) { // clear existing tile data and nodes memset(node_counts, 0, sizeof(node_counts)); - memset(tile_buffer, 0, tile_buffer_size); + memset(tile_buffer, 0, PP_TILE_BUFFER_SIZE * PP_TILE_BUFFER_SIZE); // build the nodes for each pp_path_t for(uint32_t i = 0; i < polygon->count; i++) { @@ -592,17 +549,15 @@ void pp_render(pp_poly_t *polygon) { debug(" : render the tile\n"); // render the tile - pp_rect_t rb = render_nodes(tile_buffer, &tb); + pp_rect_t rb = render_nodes(&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 + .stride = PP_TILE_BUFFER_SIZE, + .data = tile_buffer + rb.x + (PP_TILE_BUFFER_SIZE * rb.y) }; _pp_tile_callback(&tile); diff --git a/micropython/modules/picovector/picovector.c b/micropython/modules/picovector/picovector.c index f724447f0..f5921ea20 100644 --- a/micropython/modules/picovector/picovector.c +++ b/micropython/modules/picovector/picovector.c @@ -114,6 +114,8 @@ static const mp_map_elem_t VECTOR_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_NONE), MP_ROM_INT(0) }, { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_X4), MP_ROM_INT(1) }, { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_X16), MP_ROM_INT(2) }, + { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_FAST), MP_ROM_INT(1) }, + { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_BEST), MP_ROM_INT(2) }, }; static MP_DEFINE_CONST_DICT(mp_module_VECTOR_globals, VECTOR_globals_table); From 6f092a29e0db2f5a9edd97ca8212ade61968d7cc Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 22 Jul 2024 17:33:52 +0100 Subject: [PATCH 21/35] PicoVector: Rewrite around new linked-lists poly. --- .../pico_graphics_pen_rgb332.cpp | 21 +- .../pico_graphics_pen_rgb565.cpp | 14 +- libraries/pico_vector/alright-fonts.h | 27 +-- libraries/pico_vector/pico_vector.cpp | 8 +- libraries/pico_vector/pico_vector.hpp | 14 +- libraries/pico_vector/pretty-poly.h | 182 +++++++++++++----- micropython/modules/picovector/picovector.cpp | 93 +++++---- 7 files changed, 236 insertions(+), 123 deletions(-) diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp index cdb24da2c..88fceba7d 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp @@ -151,11 +151,28 @@ namespace pimoroni { uint8_t *pdest = &((uint8_t *)frame_buffer)[tile->x + ((tile->y + y) * bounds.w)]; for(int x = 0; x < tile->w; x++) { uint8_t alpha = *palpha; + uint8_t dest = *pdest; // TODO: Try to alpha blend RGB332... somewhat? - if(alpha == 0) { - } else { + if(alpha == 255) { *pdest = color; + }else if(alpha == 0) { + }else{ + // blend tha pixel + uint16_t sr = (color & 0b11100000) >> 5; + uint16_t sg = (color & 0b00011100) >> 2; + uint16_t sb = (color & 0b00000011); + + uint16_t dr = (dest & 0b11100000) >> 5; + uint16_t dg = (dest & 0b00011100) >> 2; + uint16_t db = (dest & 0b00000011); + + uint8_t r = ((sr * alpha) + (dr * (255 - alpha))) >> 8; + uint8_t g = ((sg * alpha) + (dg * (255 - alpha))) >> 8; + uint8_t b = ((sb * alpha) + (db * (255 - alpha))) >> 8; + + // recombine the channels + *pdest = (r << 5) | (g << 2) | (b); } pdest++; diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp index 6cf2d8638..5e9d5f676 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp @@ -107,20 +107,20 @@ namespace pimoroni { }else if(alpha == 0) { }else{ // blend tha pixel - uint16_t sr = (color & 0b1111100000000000) >> 11; - uint16_t sg = (color & 0b0000011111100000) >> 5; - uint16_t sb = (color & 0b0000000000011111); + uint16_t sr = (__builtin_bswap16(color) & 0b1111100000000000) >> 11; + uint16_t sg = (__builtin_bswap16(color) & 0b0000011111100000) >> 5; + uint16_t sb = (__builtin_bswap16(color) & 0b0000000000011111); - uint16_t dr = (dest & 0b1111100000000000) >> 11; - uint16_t dg = (dest & 0b0000011111100000) >> 5; - uint16_t db = (dest & 0b0000000000011111); + uint16_t dr = (__builtin_bswap16(dest) & 0b1111100000000000) >> 11; + uint16_t dg = (__builtin_bswap16(dest) & 0b0000011111100000) >> 5; + uint16_t db = (__builtin_bswap16(dest) & 0b0000000000011111); uint8_t r = ((sr * alpha) + (dr * (255 - alpha))) >> 8; uint8_t g = ((sg * alpha) + (dg * (255 - alpha))) >> 8; uint8_t b = ((sb * alpha) + (db * (255 - alpha))) >> 8; // recombine the channels - *pdest = (r << 11) | (g << 5) | (b); + *pdest = __builtin_bswap16((r << 11) | (g << 5) | (b)); } pdest++; diff --git a/libraries/pico_vector/alright-fonts.h b/libraries/pico_vector/alright-fonts.h index e598764ba..9e5a6ce9d 100644 --- a/libraries/pico_vector/alright-fonts.h +++ b/libraries/pico_vector/alright-fonts.h @@ -216,27 +216,20 @@ af_glyph_t *find_glyph(af_face_t *face, char c) { 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_poly_t *poly = pp_poly_new(); + for(uint32_t i = 0; i < glyph->path_count; i++) { + pp_path_t *path = pp_poly_add_path(poly); + for(uint32_t j = 0; j < glyph->paths[i].point_count; j++) { + pp_path_add_point(path, { + glyph->paths[i].points[j].x, + glyph->paths[i].points[j].y + }); } } - pp_render(&poly); + 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); + pp_poly_free(poly); } void af_render_character(af_face_t *face, const char c, af_text_metrics_t *tm) { diff --git a/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp index 9c20f0735..a3cb6bfce 100644 --- a/libraries/pico_vector/pico_vector.cpp +++ b/libraries/pico_vector/pico_vector.cpp @@ -31,7 +31,7 @@ namespace pimoroni { } void PicoVector::transform(pp_path_t *path, pp_mat3_t *t) { - for (auto j = 0u; j < path->count; j++) { + for (auto j = 0; j < path->count; j++) { path->points[j] = pp_point_transform(&path->points[j], t); } } @@ -51,8 +51,10 @@ namespace pimoroni { } void PicoVector::transform(pp_poly_t *poly, pp_mat3_t *t) { - for (auto i = 0u; i < poly->count; i++) { - transform(&poly->paths[i], t); + pp_path_t *path = poly->paths; + while(path) { + transform(path, t); + path = path->next; } } diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index 1bcaf3406..db0a3412a 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -109,13 +109,19 @@ namespace pimoroni { void draw(pp_poly_t *poly, pp_mat3_t *t); void draw(pp_path_t *path) { - pp_poly_t poly = {.paths = path, .count = 1}; - draw(&poly); + pp_poly_t *poly = pp_poly_new(); + poly->paths = path; + draw(poly); + poly->paths = NULL; // Don't free our non-owned path + pp_poly_free(poly); }; void draw(pp_path_t *path, pp_mat3_t *t) { - pp_poly_t poly = {.paths = path, .count = 1}; - draw(&poly, t); + pp_poly_t *poly = pp_poly_new(); + poly->paths = path; + draw(poly, t); + poly->paths = NULL; // Don't free our non-owned path + pp_poly_free(poly); }; static constexpr size_t pretty_poly_buffer_size() { diff --git a/libraries/pico_vector/pretty-poly.h b/libraries/pico_vector/pretty-poly.h index c7a7c10ec..9c746837a 100644 --- a/libraries/pico_vector/pretty-poly.h +++ b/libraries/pico_vector/pretty-poly.h @@ -100,15 +100,26 @@ typedef struct { uint8_t *data; } pp_tile_t; -typedef struct { +typedef struct _pp_path_t { pp_point_t *points; - uint32_t count; + int count; // number of points currently stored in points buffer + int storage; // size of *points buffer + struct _pp_path_t *next; // next path in the linked list } pp_path_t; +void pp_path_add_point(pp_path_t *path, pp_point_t p); +void pp_path_add_points(pp_path_t *path, pp_point_t *p, int count); +void pp_path_add_path(pp_path_t *path, pp_path_t *other); +pp_rect_t pp_path_bounds(const pp_path_t *c); typedef struct { - pp_path_t *paths; - uint32_t count; + pp_path_t *paths; } pp_poly_t; +pp_poly_t *pp_poly_new(); +void pp_poly_free(pp_poly_t *poly); +pp_path_t* pp_poly_tail_path(pp_poly_t *p); +pp_path_t* pp_poly_add_path(pp_poly_t *p); +pp_rect_t pp_poly_bounds(pp_poly_t *p); +int pp_poly_path_count(pp_poly_t *p); // user settings typedef void (*pp_tile_callback_t)(const pp_tile_t *tile); @@ -124,8 +135,6 @@ 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 } @@ -249,25 +258,102 @@ 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) * PP_TILE_BUFFER_SIZE]; } +pp_poly_t *pp_poly_new() { + pp_poly_t *poly = (pp_poly_t *)PP_MALLOC(sizeof(pp_poly_t)); + poly->paths = NULL; + return poly; +} + +void pp_poly_free(pp_poly_t *poly) { + if(poly->paths) { + pp_path_t *path = poly->paths; + while(path) { + PP_FREE(path->points); + pp_path_t *free_path = path; + path = path->next; + PP_FREE(free_path); + } + } + PP_FREE(poly); +} + +// polygon and path implementation +pp_path_t* pp_poly_tail_path(pp_poly_t *poly) { + pp_path_t *path = poly->paths; + while(path->next) path = path->next; + return path; +} + +int pp_poly_path_count(pp_poly_t *poly) { + if(!poly->paths) return 0; + pp_path_t *path = poly->paths; + int i = 0; + while(path->next) { + i++; + path = path->next; + } + return i; +} + +pp_path_t* pp_poly_add_path(pp_poly_t *poly) { + pp_path_t *path = (pp_path_t *)PP_MALLOC(sizeof(pp_path_t)); + memset(path, 0, sizeof(pp_path_t)); + path->storage = 8; + path->points = (pp_point_t *)PP_MALLOC(sizeof(pp_point_t) * path->storage); + + if(!poly->paths) { + poly->paths = path; + }else{ + pp_path_t *tail = pp_poly_tail_path(poly); + tail->next = path; + } + + return path; +} + +pp_point_t* pp_path_tail_point(pp_path_t *path) { + return (path->count > 0) ? &path->points[path->count -1] : NULL; +} + +void pp_path_add_point(pp_path_t *path, pp_point_t p) { + if(path->count == path->storage) { // no storage left, double buffer size + path->points = (pp_point_t *)PP_REALLOC(path->points, sizeof(pp_point_t) * (path->storage * 2)); + path->storage *= 2; + } + path->points[path->count] = p; + path->count++; +} + +void pp_path_add_points(pp_path_t *path, pp_point_t *points, int count) { + if(count + path->count > path->storage) { // not enough storage, allocate + path->points = (pp_point_t *)PP_REALLOC(path->points, sizeof(pp_point_t) * (count + path->count)); + path->storage = count + path->count; + } + memcpy(&path->points[count], points, sizeof(pp_point_t) * count); + path->count += count; +} + // 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_path_bounds(const pp_path_t *path) { + int minx = INT_MAX, maxx = -INT_MAX, miny = INT_MAX, maxy = -INT_MAX; + for(int i = 0; i < path->count; i++) { + minx = _pp_min(minx, path->points[i].x); + miny = _pp_min(miny, path->points[i].y); + maxx = _pp_max(maxx, path->points[i].x); + maxy = _pp_max(maxy, path->points[i].y); + } + return (pp_rect_t){minx, miny, maxx - minx, 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]); + pp_path_t *path = p->paths; + if(!path) return (pp_rect_t){}; + pp_rect_t b = pp_path_bounds(path); + path = path->next; + while(path) { + pp_rect_t cb = pp_path_bounds(path); b = pp_rect_merge(&b, &cb); + path = path->next; } return b; } @@ -391,30 +477,24 @@ void add_line_segment_to_nodes(const pp_point_t start, const pp_point_t end) { //#endif } -void build_nodes(pp_path_t *contour, pp_rect_t *bounds) { +void build_nodes(pp_path_t *path, pp_rect_t *tb) { 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 - }; + pp_point_t tile_origin = (pp_point_t){tb->x * aa_scale, tb->y * aa_scale}; // start with the last point to close the loop, transform it, scale for antialiasing, and offset to tile origin - pp_point_t last = contour->points[contour->count - 1]; + pp_point_t last = path->points[path->count - 1]; 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++) { - // fetch next point, transform it, scale for antialiasing, and offset to tile origin - pp_point_t point = contour->points[i]; - 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; + + for(int i = 0; i < path->count; i++) { + pp_point_t next = path->points[i]; + if(_pp_transform) next = pp_point_transform(&next, _pp_transform); + next.x *= aa_scale; next.y *= aa_scale; + next = pp_point_sub(&next, &tile_origin); + add_line_segment_to_nodes(last, next); + last = next; } } @@ -497,20 +577,18 @@ pp_rect_t render_nodes(pp_rect_t *tb) { void pp_render(pp_poly_t *polygon) { - debug("> draw polygon with %u contours\n", polygon->count); + debug("> draw polygon with %u contours\n", pp_poly_path_count(polygon)); - if(polygon->count == 0) { - return; - } + if(!polygon->paths) return; // determine extreme bounds - pp_rect_t polygon_bounds = pp_polygon_bounds(polygon); + pp_rect_t pb = pp_polygon_bounds(polygon); if(_pp_transform) { - polygon_bounds = pp_rect_transform(&polygon_bounds, _pp_transform); + pb = pp_rect_transform(&pb, _pp_transform); } - debug(" - polygon bounds %d, %d (%d x %d)\n", polygon_bounds.x, polygon_bounds.y, polygon_bounds.w, polygon_bounds.h); + debug(" - polygon bounds %d, %d (%d x %d)\n", pb.x, pb.y, pb.w, pb.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 @@ -526,8 +604,8 @@ void pp_render(pp_poly_t *polygon) { // iterate over tiles debug(" - processing tiles\n"); - for(int32_t y = polygon_bounds.y; y < polygon_bounds.y + polygon_bounds.h; y += PP_TILE_BUFFER_SIZE) { - for(int32_t x = polygon_bounds.x; x < polygon_bounds.x + polygon_bounds.w; x += PP_TILE_BUFFER_SIZE) { + for(int32_t y = pb.y; y < pb.y + pb.h; y += PP_TILE_BUFFER_SIZE) { + for(int32_t x = pb.x; x < pb.x + pb.w; x += PP_TILE_BUFFER_SIZE) { pp_rect_t tb = (pp_rect_t){.x = x, .y = y, .w = PP_TILE_BUFFER_SIZE, .h = PP_TILE_BUFFER_SIZE}; tb = pp_rect_intersection(&tb, &_pp_clip); debug(" : %d, %d (%d x %d)\n", tb.x, tb.y, tb.w, tb.h); @@ -540,11 +618,13 @@ void pp_render(pp_poly_t *polygon) { memset(tile_buffer, 0, PP_TILE_BUFFER_SIZE * PP_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); - } + pp_path_t *path = polygon->paths; + if(!path) return; + do { + debug(" : build nodes for path (%d points)\n", path->count); + build_nodes(path, &tb); + path = path->next; + } while(path); debug(" : render the tile\n"); // render the tile diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index 5cba3cea6..8da08e4ff 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -27,7 +27,7 @@ typedef struct _VECTOR_obj_t { typedef struct _PATH_obj_t { mp_obj_base_t base; - pp_path_t path; + pp_path_t *path; } _PATH_obj_t; void __printf_debug_flush() { @@ -170,19 +170,19 @@ mp_obj_t RECTANGLE_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_k mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); _PATH_obj_t *self = mp_obj_malloc_with_finaliser(_PATH_obj_t, &POLYGON_type); + self->path = (pp_path_t *)PP_MALLOC(sizeof(pp_path_t)); + self->path->storage = 4; + self->path->points = (pp_point_t *)PP_MALLOC(sizeof(pp_point_t) * self->path->storage); picovector_point_type x = mp_picovector_get_point_type(args[ARG_x].u_obj); picovector_point_type y = mp_picovector_get_point_type(args[ARG_y].u_obj); picovector_point_type w = mp_picovector_get_point_type(args[ARG_w].u_obj); picovector_point_type h = mp_picovector_get_point_type(args[ARG_h].u_obj); - self->path.points = m_new(pp_point_t, 4); - self->path.count = 4; - - self->path.points[0] = {picovector_point_type(x), picovector_point_type(y)}; - self->path.points[1] = {picovector_point_type(x + w), picovector_point_type(y)}; - self->path.points[2] = {picovector_point_type(x + w), picovector_point_type(y + h)}; - self->path.points[3] = {picovector_point_type(x), picovector_point_type(y + h)}; + pp_path_add_point(self->path, {picovector_point_type(x), picovector_point_type(y)}); + pp_path_add_point(self->path, {picovector_point_type(x + w), picovector_point_type(y)}); + pp_path_add_point(self->path, {picovector_point_type(x + w), picovector_point_type(y + h)}); + pp_path_add_point(self->path, {picovector_point_type(x), picovector_point_type(y + h)}); return self; } @@ -215,15 +215,17 @@ mp_obj_t REGULAR_POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size float angle = (360.0f / sides) * (M_PI / 180.0f); - self->path.points = m_new(pp_point_t, sides); - self->path.count = sides; + + self->path = (pp_path_t *)PP_MALLOC(sizeof(pp_path_t)); + self->path->storage = sides; + self->path->points = (pp_point_t *)PP_MALLOC(sizeof(pp_point_t) * self->path->storage); for(auto s = 0u; s < sides; s++) { float current_angle = angle * s + rotation; - self->path.points[s] = { + pp_path_add_point(self->path, { (picovector_point_type)(cos(current_angle) * radius) + o_x, (picovector_point_type)(sin(current_angle) * radius) + o_y - }; + }); } return self; @@ -235,10 +237,11 @@ mp_obj_t POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, size_t num_points = n_args; const mp_obj_t *points = all_args; - if(num_points < 3) mp_raise_ValueError("Polygon: At least 3 points required."); + self->path = (pp_path_t *)PP_MALLOC(sizeof(pp_path_t)); + self->path->storage = num_points; + self->path->points = (pp_point_t *)PP_MALLOC(sizeof(pp_point_t) * self->path->storage); - self->path.points = m_new(pp_point_t, num_points); - self->path.count = num_points; + if(num_points < 3) mp_raise_ValueError("Polygon: At least 3 points required."); for(auto i = 0u; i < num_points; i++) { mp_obj_t c_obj = points[i]; @@ -249,10 +252,10 @@ mp_obj_t POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, if(t_point->len != 2) mp_raise_ValueError("Tuple must have X, Y"); - self->path.points[i] = { + pp_path_add_point(self->path, { (picovector_point_type)mp_picovector_get_point_type(t_point->items[0]), (picovector_point_type)mp_picovector_get_point_type(t_point->items[1]), - }; + }); } return self; @@ -264,13 +267,13 @@ mp_obj_t POLYGON_centroid(mp_obj_t self_in) { PP_COORD_TYPE sum_x = (PP_COORD_TYPE)0; PP_COORD_TYPE sum_y = (PP_COORD_TYPE)0; - for(auto i = 0u; i < self->path.count; i++) { - sum_x += self->path.points[i].x; - sum_y += self->path.points[i].y; + for(auto i = 0; i < self->path->count; i++) { + sum_x += self->path->points[i].x; + sum_y += self->path->points[i].y; } - sum_x /= (float)self->path.count; - sum_y /= (float)self->path.count; + sum_x /= (float)self->path->count; + sum_y /= (float)self->path->count; mp_obj_t tuple[2]; tuple[0] = mp_picovector_set_point_type((int)(sum_x)); @@ -282,7 +285,7 @@ mp_obj_t POLYGON_centroid(mp_obj_t self_in) { mp_obj_t POLYGON_bounds(mp_obj_t self_in) { _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); - pp_rect_t bounds = pp_contour_bounds(&self->path); + pp_rect_t bounds = pp_path_bounds(self->path); mp_obj_t tuple[4]; tuple[0] = mp_picovector_set_point_type((int)(bounds.x)); @@ -297,10 +300,10 @@ void POLYGON_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t ki (void)kind; _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); - pp_rect_t bounds = pp_contour_bounds(&self->path); + pp_rect_t bounds = pp_path_bounds(self->path); mp_print_str(print, "Polygon(points = "); - mp_obj_print_helper(print, mp_picovector_set_point_type(self->path.count), PRINT_REPR); + mp_obj_print_helper(print, mp_picovector_set_point_type(self->path->count), PRINT_REPR); mp_print_str(print, ", bounds = "); mp_obj_print_helper(print, mp_picovector_set_point_type(bounds.x), PRINT_REPR); mp_print_str(print, ", "); @@ -314,7 +317,8 @@ void POLYGON_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t ki mp_obj_t POLYGON__del__(mp_obj_t self_in) { _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); - (void)self; + PP_FREE(self->path->points); + PP_FREE(self->path); // TODO: Do we actually need to free anything here, if it's on GC heap it should get collected return mp_const_none; } @@ -323,7 +327,7 @@ typedef struct _mp_obj_polygon_it_t { mp_obj_base_t base; mp_fun_1_t iternext; mp_obj_t polygon; - size_t cur; + int cur; } mp_obj_polygon_it_t; static mp_obj_t py_path_it_iternext(mp_obj_t self_in) { @@ -332,11 +336,11 @@ static mp_obj_t py_path_it_iternext(mp_obj_t self_in) { //mp_printf(&mp_plat_print, "points: %d, current: %d\n", polygon->contour.count, self->cur); - if(self->cur >= path->path.count) return MP_OBJ_STOP_ITERATION; + if(self->cur >= path->path->count) return MP_OBJ_STOP_ITERATION; mp_obj_t tuple[2]; - tuple[0] = mp_picovector_set_point_type((int)(path->path.points[self->cur].x)); - tuple[1] = mp_picovector_set_point_type((int)(path->path.points[self->cur].y)); + tuple[0] = mp_picovector_set_point_type((int)(path->path->points[self->cur].x)); + tuple[1] = mp_picovector_set_point_type((int)(path->path->points[self->cur].y)); self->cur++; return mp_obj_new_tuple(2, tuple); @@ -484,7 +488,7 @@ mp_obj_t VECTOR_rotate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_arg float angle = mp_obj_get_float(args[ARG_angle].u_obj); - self->vector->rotate(&poly->path, origin, angle); + self->vector->rotate(poly->path, origin, angle); return mp_const_none; } @@ -509,7 +513,7 @@ mp_obj_t VECTOR_translate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ pp_point_t translate = {(PP_COORD_TYPE)args[ARG_x].u_int, (PP_COORD_TYPE)args[ARG_y].u_int}; - self->vector->translate(&poly->path, translate); + self->vector->translate(poly->path, translate); return mp_const_none; } @@ -521,23 +525,34 @@ mp_obj_t VECTOR_draw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(pos_args[0], _VECTOR_obj_t); - pp_poly_t group; - group.count = num_polygons; - group.paths = (pp_path_t *)m_new(pp_path_t, num_polygons); + if(num_polygons == 1) { + mp_obj_t poly_obj = polygons[0]; + + if(!MP_OBJ_IS_TYPE(poly_obj, &POLYGON_type)) mp_raise_TypeError("draw: Polygon required."); + + _PATH_obj_t *poly = MP_OBJ_TO_PTR2(poly_obj, _PATH_obj_t); + + self->vector->draw(poly->path); + + return mp_const_none; + } + + + pp_poly_t *group = pp_poly_new(); for(auto i = 0u; i < num_polygons; i++) { + pp_path_t *path = pp_poly_add_path(group); mp_obj_t poly_obj = polygons[i]; if(!MP_OBJ_IS_TYPE(poly_obj, &POLYGON_type)) mp_raise_TypeError("draw: Polygon required."); _PATH_obj_t *poly = MP_OBJ_TO_PTR2(poly_obj, _PATH_obj_t); - group.paths[i].points = poly->path.points; - group.paths[i].count = poly->path.count; + pp_path_add_points(path, poly->path->points, poly->path->count); } - self->vector->draw(&group); + self->vector->draw(group); - m_free(group.paths); + pp_poly_free(group); return mp_const_none; } From 841225efbffdc1a9708070b03d7974f8dbf0f7ed Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 23 Jul 2024 10:58:33 +0100 Subject: [PATCH 22/35] PicoVector: Update C++ examples. --- .../pico_display_2/pico_display_2_vector.cpp | 13 +++++++------ .../pico_w_explorer/pico_w_explorer_vector.cpp | 17 +++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/examples/pico_display_2/pico_display_2_vector.cpp b/examples/pico_display_2/pico_display_2_vector.cpp index 68e915542..47c2a468e 100644 --- a/examples/pico_display_2/pico_display_2_vector.cpp +++ b/examples/pico_display_2/pico_display_2_vector.cpp @@ -42,16 +42,15 @@ int main() { pp_point_t outline[] = {{-64, -64}, {64, -64}, {64, 64}, {-64, 64}}; pp_point_t hole[] = {{ -32, 32}, { 32, 32}, { 32, -32}, { -32, -32}}; - pp_path_t paths[] = { - {.points = outline, .count = 4}, - {.points = hole, .count = 4} - }; - pp_poly_t poly = {.paths = paths, .count = 2}; + + pp_poly_t *poly = pp_poly_new(); + pp_path_add_points(pp_poly_add_path(poly), outline, sizeof(outline) / sizeof(pp_point_t)); + pp_path_add_points(pp_poly_add_path(poly), hole, sizeof(hole) / sizeof(pp_point_t)); pp_mat3_t pos = pp_mat3_identity(); pp_mat3_translate(&pos, 50, 50); pp_mat3_rotate(&pos, a); - vector.draw(&poly); + vector.draw(poly); vector.text("Hello World", &pos); // update screen @@ -60,6 +59,8 @@ int main() { if (a > 359) { a = 0; } + + pp_poly_free(poly); } return 0; diff --git a/examples/pico_w_explorer/pico_w_explorer_vector.cpp b/examples/pico_w_explorer/pico_w_explorer_vector.cpp index 20db5732e..82cc29d8e 100644 --- a/examples/pico_w_explorer/pico_w_explorer_vector.cpp +++ b/examples/pico_w_explorer/pico_w_explorer_vector.cpp @@ -31,16 +31,15 @@ int main() { pp_point_t outline[] = {{-128, -128}, {128, -128}, {128, 128}, {-128, 128}}; pp_point_t hole[] = {{ -64, 64}, { 64, 64}, { 64, -64}, { -64, -64}}; - pp_path_t paths[] = { - {.points = outline, .count = 4}, - {.points = hole, .count = 4} - }; - pp_poly_t poly = {.paths = paths, .count = 2}; - vector.rotate(&poly, {0, 0}, angle); - vector.translate(&poly, {160, 120}); + pp_poly_t *poly = pp_poly_new(); + pp_path_add_points(pp_poly_add_path(poly), outline, sizeof(outline) / sizeof(pp_point_t)); + pp_path_add_points(pp_poly_add_path(poly), hole, sizeof(hole) / sizeof(pp_point_t)); - vector.draw(&poly); + vector.rotate(poly, {0, 0}, angle); + vector.translate(poly, {160, 120}); + + vector.draw(poly); //pp_mat3_t t = pp_mat3_identity(); //vector.text("Hello World", {0, 0}, &t); @@ -49,6 +48,8 @@ int main() { st7789.update(&graphics); angle += 1.0f; + + pp_poly_free(poly); } return 0; From a123cfae52877d5355a8f1ae2d68f937f9a7e06e Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 25 Jul 2024 15:07:26 +0100 Subject: [PATCH 23/35] PicoVector: Big refactor, ppp primitives. * Remove Polygon types in favour of primitives .circle, .rectangle etc * Add a new Transform type for building up transformation matrices * Add support to set/clear transform on drawing --- libraries/pico_vector/pico_vector.cpp | 2 + libraries/pico_vector/pico_vector.hpp | 3 +- .../pico_vector/pretty-poly-primitives.h | 181 +++++++ libraries/pico_vector/pretty-poly.h | 96 ++-- .../examples/tufty2040/vector_clock.py | 123 +++-- micropython/modules/picovector/picovector.c | 111 ++-- micropython/modules/picovector/picovector.cpp | 500 +++++++++++------- micropython/modules/picovector/picovector.h | 30 +- 8 files changed, 710 insertions(+), 336 deletions(-) create mode 100644 libraries/pico_vector/pretty-poly-primitives.h diff --git a/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp index a3cb6bfce..a783135c1 100644 --- a/libraries/pico_vector/pico_vector.cpp +++ b/libraries/pico_vector/pico_vector.cpp @@ -1,8 +1,10 @@ #define PP_IMPLEMENTATION #define AF_IMPLEMENTATION +#define PPP_IMPLEMENTATION #include "pico_vector.hpp" #include + namespace pimoroni { PicoGraphics *PicoVector::graphics = nullptr; diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index db0a3412a..97a5233fe 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -17,6 +17,7 @@ #define AF_DEBUG(...) af_debug(__VA_ARGS__) #include "pretty-poly.h" +#include "pretty-poly-primitives.h" #include "alright-fonts.h" #include "pico_graphics.hpp" @@ -29,10 +30,10 @@ namespace pimoroni { class PicoVector { private: - static PicoGraphics *graphics; af_text_metrics_t text_metrics; public: + static PicoGraphics *graphics; PicoVector(PicoGraphics *graphics, void *mem = nullptr) { PicoVector::graphics = graphics; diff --git a/libraries/pico_vector/pretty-poly-primitives.h b/libraries/pico_vector/pretty-poly-primitives.h new file mode 100644 index 000000000..b6b01f1fe --- /dev/null +++ b/libraries/pico_vector/pretty-poly-primitives.h @@ -0,0 +1,181 @@ +/* + + 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 PPP_INCLUDE_H +#define PPP_INCLUDE_H + +#include "pretty-poly.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + PP_COORD_TYPE x, y, w, h; // coordinates + PP_COORD_TYPE s; // stroke thickness (0 == filled) + PP_COORD_TYPE r1, r2, r3, r4; // corner radii (r1 = top left then clockwise) +} ppp_rect_def; + +typedef struct { + PP_COORD_TYPE x, y; // coordinates + PP_COORD_TYPE r; // radius + int e; // edge count + PP_COORD_TYPE s; // stroke thickness (0 == filled) +} ppp_regular_def; + +typedef struct { + PP_COORD_TYPE x, y; // coordinates + PP_COORD_TYPE r; // radius + PP_COORD_TYPE s; // stroke thickness (0 == filled) +} ppp_circle_def; + +typedef struct { + PP_COORD_TYPE x, y; // coordinates + PP_COORD_TYPE r; // radius + PP_COORD_TYPE s; // stroke thickness (0 == filled) + PP_COORD_TYPE f, t; // angle from and to +} ppp_arc_def; + +pp_poly_t* ppp_rect(ppp_rect_def d); +pp_poly_t* ppp_regular(ppp_regular_def d); +pp_poly_t* ppp_circle(ppp_circle_def d); +pp_poly_t* ppp_arc(ppp_arc_def d); + +#ifdef __cplusplus +} +#endif + +#ifdef PPP_IMPLEMENTATION + +void _pp_round_rect_corner_points(pp_path_t *path, PP_COORD_TYPE cx, PP_COORD_TYPE cy, PP_COORD_TYPE r, int q) { + float quality = 5; // higher the number, lower the quality - selected by experiment + int steps = ceil(r / quality) + 2; // + 2 to include start and end + float delta = -(M_PI / 2) / steps; + float theta = (M_PI / 2) * q; // select start theta for this quadrant + for(int i = 0; i <= steps; i++) { + PP_COORD_TYPE xo = sin(theta) * r, yo = cos(theta) * r; + pp_path_add_point(path, (pp_point_t){cx + xo, cy + yo}); + theta += delta; + } +} + +void _ppp_rrect_corner(pp_path_t *path, PP_COORD_TYPE cx, PP_COORD_TYPE cy, PP_COORD_TYPE r, int q) { + float quality = 5; // higher the number, lower the quality - selected by experiment + int steps = ceil(r / quality) + 2; // + 2 to include start and end + float delta = -(M_PI / 2) / steps; + float theta = (M_PI / 2) * q; // select start theta for this quadrant + for(int i = 0; i <= steps; i++) { + PP_COORD_TYPE xo = sin(theta) * r, yo = cos(theta) * r; + pp_path_add_point(path, (pp_point_t){cx + xo, cy + yo}); + theta += delta; + } +} + +void _ppp_rrect_path(pp_path_t *path, ppp_rect_def d) { + d.r1 == 0 ? pp_path_add_point(path, (pp_point_t){d.x, d.y}) : _ppp_rrect_corner(path, d.x + d.r1, d.y + d.r1, d.r1, 3); + d.r2 == 0 ? pp_path_add_point(path, (pp_point_t){d.x + d.w, d.y}) : _ppp_rrect_corner(path, d.x + d.w - d.r2, d.y + d.r2, d.r2, 2); + d.r3 == 0 ? pp_path_add_point(path, (pp_point_t){d.x + d.w, d.y + d.h}) : _ppp_rrect_corner(path, d.x + d.w - d.r3, d.y + d.h - d.r3, d.r3, 1); + d.r4 == 0 ? pp_path_add_point(path, (pp_point_t){d.x, d.y}) : _ppp_rrect_corner(path, d.x + d.r4, d.y + d.h - d.r4, d.r4, 0); +} + +pp_poly_t* ppp_rect(ppp_rect_def d) { + pp_poly_t *poly = pp_poly_new(); + pp_path_t *path = pp_poly_add_path(poly); + if(d.r1 == 0.0f && d.r2 == 0.0f && d.r3 == 0.0f && d.r4 == 0.0f) { // non rounded rect + pp_point_t points[] = {{d.x, d.y}, {d.x + d.w, d.y}, {d.x + d.w, d.y + d.h}, {d.x, d.y + d.h}}; + pp_path_add_points(path, points, 4); + if(d.s != 0) { // stroked, not filled + d.x += d.s; d.y += d.s; d.w -= 2 * d.s; d.h -= 2 * d.s; + pp_path_t *inner = pp_poly_add_path(poly); + pp_point_t points[] = {{d.x, d.y}, {d.x + d.w, d.y}, {d.x + d.w, d.y + d.h}, {d.x, d.y + d.h}}; + pp_path_add_points(inner, points, 4); + } + }else{ // rounded rect + _ppp_rrect_path(path, d); + if(d.s != 0) { // stroked, not filled + d.x += d.s; d.y += d.s; d.w -= 2 * d.s; d.h -= 2 * d.s; + d.r1 = _pp_max(0, d.r1 - d.s); + d.r2 = _pp_max(0, d.r2 - d.s); + d.r3 = _pp_max(0, d.r3 - d.s); + d.r4 = _pp_max(0, d.r4 - d.s); + pp_path_t *inner = pp_poly_add_path(poly); + _ppp_rrect_path(inner, d); + } + } + return poly; +} + +pp_poly_t* ppp_regular(ppp_regular_def d) { + pp_poly_t *poly = pp_poly_new(); + pp_path_t *path = pp_poly_add_path(poly); + pp_path_t *inner = d.s != 0.0f ? pp_poly_add_path(poly) : NULL; + for(int i = 0; i < d.e; i++) { + float theta = ((M_PI * 2.0f) / (float)d.e) * (float)i; + pp_path_add_point(path, (pp_point_t){sin(theta) * d.r + d.x, cos(theta) * d.r + d.y}); + if(inner) { + pp_path_add_point(inner, (pp_point_t){sin(theta) * (d.r - d.s) + d.x, cos(theta) * (d.r - d.s) + d.y}); + } + } + return poly; +} + +pp_poly_t* ppp_circle(ppp_circle_def d) { + int e = _pp_max(8, d.r); // edge count + ppp_regular_def r = {d.x, d.y, d.r, e, d.s}; + return ppp_regular(r); +} + +pp_poly_t* ppp_arc(ppp_arc_def d) { + pp_poly_t *poly = pp_poly_new(); + pp_path_t *path = pp_poly_add_path(poly); + pp_path_t *inner = (pp_path_t *)(d.s == 0.0f ? NULL : calloc(1, sizeof(pp_path_t))); + + // no thickness, so add centre point to make pie shape + if(!inner) pp_path_add_point(path, (pp_point_t){d.x, d.y}); + + d.f = d.f * (M_PI / 180.0f); d.t = d.t * (M_PI / 180.0f); // to radians + int s = _pp_max(8, d.r); float astep = (d.t - d.f) / s; float a = d.f; + for(int i = 0; i <= s; i++) { + pp_path_add_point(path, (pp_point_t){sin(a) * d.r + d.x, cos(a) * d.r + d.y}); + if(inner) { + pp_path_add_point(inner, (pp_point_t){sin(d.t - (a - d.f)) * (d.r - d.s) + d.x, cos(d.t - (a - d.f)) * (d.r - d.s) + d.y}); + } + a += astep; + } + + if(inner) { // append the inner path + pp_path_add_points(path, inner->points, inner->count); + free(inner->points); free(inner); + } + + return poly; +} + +#endif // PPP_IMPLEMENTATION + +#endif // PPP_INCLUDE_H \ No newline at end of file diff --git a/libraries/pico_vector/pretty-poly.h b/libraries/pico_vector/pretty-poly.h index 9c746837a..5fc1f80fb 100644 --- a/libraries/pico_vector/pretty-poly.h +++ b/libraries/pico_vector/pretty-poly.h @@ -6,8 +6,8 @@ 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. + 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 @@ -21,9 +21,9 @@ Contributor bwaaaaaarks! 🦜 - @MichaelBell - lots of bug fixes, performance boosts, and suggestions. + @MichaelBell - lots of bug fixes, performance boosts, and suggestions. @gadgetoid - integrating into the PicoVector library and testing. - + */ #ifndef PP_INCLUDE_H @@ -84,7 +84,7 @@ pp_point_t pp_point_transform(pp_point_t *p, pp_mat3_t *m); // rect type typedef struct { - int32_t x, y, w, h; + 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); @@ -109,10 +109,11 @@ typedef struct _pp_path_t { void pp_path_add_point(pp_path_t *path, pp_point_t p); void pp_path_add_points(pp_path_t *path, pp_point_t *p, int count); void pp_path_add_path(pp_path_t *path, pp_path_t *other); +void pp_path_union(pp_path_t *path, pp_path_t *other); pp_rect_t pp_path_bounds(const pp_path_t *c); typedef struct { - pp_path_t *paths; + pp_path_t *paths; } pp_poly_t; pp_poly_t *pp_poly_new(); void pp_poly_free(pp_poly_t *poly); @@ -120,6 +121,7 @@ pp_path_t* pp_poly_tail_path(pp_poly_t *p); pp_path_t* pp_poly_add_path(pp_poly_t *p); pp_rect_t pp_poly_bounds(pp_poly_t *p); int pp_poly_path_count(pp_poly_t *p); +void pp_poly_merge(pp_poly_t *p, pp_poly_t *m); // user settings typedef void (*pp_tile_callback_t)(const pp_tile_t *tile); @@ -186,7 +188,7 @@ void pp_mat3_mul(pp_mat3_t *m1, pp_mat3_t *m2) { 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; + r.v22 = m1->v20 * m2->v02 + m1->v21 * m2->v12 + m1->v22 * m2->v22; *m1 = r; } @@ -223,7 +225,7 @@ 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) { return (pp_rect_t){ - .x = _pp_min(r1->x, r2->x), + .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) @@ -246,9 +248,9 @@ pp_rect_t pp_rect_transform(pp_rect_t *r, pp_mat3_t *m) { 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), + .x = (int32_t)minx, + .y = (int32_t)miny, + .w = (int32_t)(maxx - minx), .h = (int32_t)(maxy - miny) }; } @@ -295,7 +297,7 @@ int pp_poly_path_count(pp_poly_t *poly) { return i; } -pp_path_t* pp_poly_add_path(pp_poly_t *poly) { +pp_path_t* pp_poly_add_path(pp_poly_t *poly) { pp_path_t *path = (pp_path_t *)PP_MALLOC(sizeof(pp_path_t)); memset(path, 0, sizeof(pp_path_t)); path->storage = 8; @@ -307,18 +309,34 @@ pp_path_t* pp_poly_add_path(pp_poly_t *poly) { pp_path_t *tail = pp_poly_tail_path(poly); tail->next = path; } - + return path; } +void pp_poly_merge(pp_poly_t *p, pp_poly_t *m) { + if(!p->paths) { + p->paths = m->paths; + }else{ + pp_poly_tail_path(p)->next = m->paths; + } + + m->paths = NULL; + pp_poly_free(m); +} + pp_point_t* pp_path_tail_point(pp_path_t *path) { return (path->count > 0) ? &path->points[path->count -1] : NULL; } void pp_path_add_point(pp_path_t *path, pp_point_t p) { if(path->count == path->storage) { // no storage left, double buffer size - path->points = (pp_point_t *)PP_REALLOC(path->points, sizeof(pp_point_t) * (path->storage * 2)); - path->storage *= 2; + if(path->points) { + path->storage *= 2; + path->points = (pp_point_t *)PP_REALLOC(path->points, sizeof(pp_point_t) * (path->storage)); + }else{ + path->storage = 8; + path->points = (pp_point_t *)PP_MALLOC(sizeof(pp_point_t) * (path->storage)); + } } path->points[path->count] = p; path->count++; @@ -326,28 +344,32 @@ void pp_path_add_point(pp_path_t *path, pp_point_t p) { void pp_path_add_points(pp_path_t *path, pp_point_t *points, int count) { if(count + path->count > path->storage) { // not enough storage, allocate - path->points = (pp_point_t *)PP_REALLOC(path->points, sizeof(pp_point_t) * (count + path->count)); - path->storage = count + path->count; + path->storage = path->count + count; + path->points = (pp_point_t *)PP_REALLOC(path->points, sizeof(pp_point_t) * (path->storage)); } - memcpy(&path->points[count], points, sizeof(pp_point_t) * count); - path->count += count; + memcpy(&path->points[path->count], points, sizeof(pp_point_t) * count); + path->count += count; } // pp_contour_t implementation -pp_rect_t pp_path_bounds(const pp_path_t *path) { +pp_rect_t pp_path_bounds(const pp_path_t *path) { int minx = INT_MAX, maxx = -INT_MAX, miny = INT_MAX, maxy = -INT_MAX; for(int i = 0; i < path->count; i++) { minx = _pp_min(minx, path->points[i].x); miny = _pp_min(miny, path->points[i].y); - maxx = _pp_max(maxx, path->points[i].x); + maxx = _pp_max(maxx, path->points[i].x); maxy = _pp_max(maxy, path->points[i].y); } return (pp_rect_t){minx, miny, maxx - minx, maxy - miny}; } -pp_rect_t pp_polygon_bounds(pp_poly_t *p) { +void pp_path_union(pp_path_t *path, pp_path_t *other) { + +} + +pp_rect_t pp_poly_bounds(pp_poly_t *p) { pp_path_t *path = p->paths; - if(!path) return (pp_rect_t){}; + if(!path) return (pp_rect_t){}; pp_rect_t b = pp_path_bounds(path); path = path->next; while(path) { @@ -397,13 +419,13 @@ void debug_tile(const pp_tile_t *tile) { debug("[%3d]: ", y); for(int32_t x = 0; x < tile->w; x++) { debug("%02x", pp_tile_get(tile, x, y)); - } - debug("\n"); + } + debug("\n"); } debug("-----------------------\n"); } -void add_line_segment_to_nodes(const pp_point_t start, const pp_point_t end) { +void add_line_segment_to_nodes(const pp_point_t start, const pp_point_t end, pp_rect_t *tb) { int32_t sx = start.x, sy = start.y, ex = end.x, ey = end.y; if(ey < sy) { @@ -415,13 +437,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 gradient - if (ey < 0 || sy >= (int)(PP_TILE_BUFFER_SIZE << _pp_antialias) || sy == ey) return; + if (ey < 0 || sy >= (int)(tb->h << _pp_antialias) || 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_TILE_BUFFER_SIZE << _pp_antialias), ey) - y; + int count = _pp_min((int)(tb->h << _pp_antialias), ey) - y; int x = sx; int e = 0; @@ -465,7 +487,7 @@ void add_line_segment_to_nodes(const pp_point_t start, const pp_point_t end) { while(e > dy) {e -= dy; x += xinc;} // clamp node x value to tile bounds - int nx = _pp_max(_pp_min(x, (PP_TILE_BUFFER_SIZE << _pp_antialias)), 0); + int nx = _pp_max(_pp_min(x, (tb->w << _pp_antialias)), 0); //debug(" + adding node at %d, %d\n", x, y); // add node to node list nodes[y][node_counts[y]++] = nx; @@ -482,18 +504,18 @@ void build_nodes(pp_path_t *path, pp_rect_t *tb) { pp_point_t tile_origin = (pp_point_t){tb->x * aa_scale, tb->y * aa_scale}; - // start with the last point to close the loop, transform it, scale for antialiasing, and offset to tile origin + // start with the last point to close the loop, transform it, scale for antialiasing, and offset to tile origin pp_point_t last = path->points[path->count - 1]; 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(int i = 0; i < path->count; i++) { - pp_point_t next = path->points[i]; + pp_point_t next = path->points[i]; if(_pp_transform) next = pp_point_transform(&next, _pp_transform); next.x *= aa_scale; next.y *= aa_scale; next = pp_point_sub(&next, &tile_origin); - add_line_segment_to_nodes(last, next); + add_line_segment_to_nodes(last, next, tb); last = next; } } @@ -527,7 +549,7 @@ pp_rect_t render_nodes(pp_rect_t *tb) { // update render bounds rb.x = _pp_min(rb.x, sx); - rb.y = _pp_min(rb.y, y); + rb.y = _pp_min(rb.y, y); minx = _pp_min(_pp_min(sx, ex), minx); maxx = _pp_max(_pp_max(sx, ex), maxx); rb.h = y - rb.y + 1; @@ -563,7 +585,7 @@ pp_rect_t render_nodes(pp_rect_t *tb) { #if PP_SCALE_TO_ALPHA == 1 for(int y = rb.y; y < rb.y + rb.h; y++) { unsigned char* row_data = &tile_buffer[y * PP_TILE_BUFFER_SIZE + rb.x]; - for(int x = rb.x; x < rb.x + rb.w; x++) { + for(int x = rb.x; x < rb.x + rb.w; x++) { *row_data = p_alpha_map[*row_data]; row_data++; } @@ -582,7 +604,7 @@ void pp_render(pp_poly_t *polygon) { if(!polygon->paths) return; // determine extreme bounds - pp_rect_t pb = pp_polygon_bounds(polygon); + pp_rect_t pb = pp_poly_bounds(polygon); if(_pp_transform) { pb = pp_rect_transform(&pb, _pp_transform); @@ -634,7 +656,7 @@ void pp_render(pp_poly_t *polygon) { if(pp_rect_empty(&tb)) { debug(" : empty after rendering, skipping\n"); continue; } - pp_tile_t tile = { + pp_tile_t tile = { .x = tb.x, .y = tb.y, .w = tb.w, .h = tb.h, .stride = PP_TILE_BUFFER_SIZE, .data = tile_buffer + rb.x + (PP_TILE_BUFFER_SIZE * rb.y) diff --git a/micropython/examples/tufty2040/vector_clock.py b/micropython/examples/tufty2040/vector_clock.py index afb626c9a..118acdd03 100644 --- a/micropython/examples/tufty2040/vector_clock.py +++ b/micropython/examples/tufty2040/vector_clock.py @@ -1,11 +1,11 @@ import time import gc -from picographics import PicoGraphics, DISPLAY_TUFTY_2040, PEN_RGB332 -from picovector import PicoVector, Polygon, RegularPolygon, Rectangle, ANTIALIAS_X4 +from picographics import PicoGraphics, DISPLAY_TUFTY_2040, PEN_RGB565 as PEN +from picovector import PicoVector, Transform, Polygon, ANTIALIAS_X4 -display = PicoGraphics(DISPLAY_TUFTY_2040, pen_type=PEN_RGB332) +display = PicoGraphics(DISPLAY_TUFTY_2040, pen_type=PEN) vector = PicoVector(display) vector.set_antialiasing(ANTIALIAS_X4) @@ -25,15 +25,39 @@ WIDTH, HEIGHT = display.get_bounds() -hub = RegularPolygon(int(WIDTH / 2), int(HEIGHT / 2), 24, 5) +t = Transform() -face = RegularPolygon(int(WIDTH / 2), int(HEIGHT / 2), 48, int(HEIGHT / 2)) +hub = Polygon() +hub.circle(int(WIDTH / 2), int(HEIGHT / 2), 5) + +face = Polygon() +face.circle(int(WIDTH / 2), int(HEIGHT / 2), int(HEIGHT / 2)) + +tick_mark = Polygon() +tick_mark.rectangle(int(WIDTH / 2) - 3, 10, 6, int(HEIGHT / 48)) + +hour_mark = Polygon() +hour_mark.rectangle(int(WIDTH / 2) - 5, 10, 10, int(HEIGHT / 10)) + +second_hand_length = int(HEIGHT / 2) - int(HEIGHT / 8) +second_hand = Polygon() +second_hand.path((-2, -second_hand_length), (-2, int(HEIGHT / 8)), (2, int(HEIGHT / 8)), (2, -second_hand_length)) + +minute_hand_length = int(HEIGHT / 2) - int(HEIGHT / 24) +minute_hand = Polygon() +minute_hand.path((-5, -minute_hand_length), (-10, int(HEIGHT / 16)), (10, int(HEIGHT / 16)), (5, -minute_hand_length)) + +hour_hand_length = int(HEIGHT / 2) - int(HEIGHT / 8) +hour_hand = Polygon() +hour_hand.path((-5, -hour_hand_length), (-10, int(HEIGHT / 16)), (10, int(HEIGHT / 16)), (5, -hour_hand_length)) print(time.localtime()) last_second = None +vector.set_transform(None) while True: + t.reset() t_start = time.ticks_ms() year, month, day, hour, minute, second, _, _ = time.localtime() @@ -48,72 +72,79 @@ display.set_pen(BLACK) display.circle(int(WIDTH / 2), int(HEIGHT / 2), int(HEIGHT / 2)) display.set_pen(WHITE) - display.circle(int(WIDTH / 2), int(HEIGHT / 2), int(HEIGHT / 2) - 4) + # display.circle(int(WIDTH / 2), int(HEIGHT / 2), int(HEIGHT / 2) - 4) + + vector.draw(face) display.set_pen(GREY) + vector.set_transform(t) + + t.translate(0, 2) for a in range(60): - tick_mark = Rectangle(int(WIDTH / 2) - 3, 10, 6, int(HEIGHT / 48)) - vector.rotate(tick_mark, 360 / 60.0 * a, int(WIDTH / 2), int(HEIGHT / 2)) - vector.translate(tick_mark, 0, 2) - vector.draw(tick_mark) - - for a in range(12): - hour_mark = Rectangle(int(WIDTH / 2) - 5, 10, 10, int(HEIGHT / 10)) - vector.rotate(hour_mark, 360 / 12.0 * a, int(WIDTH / 2), int(HEIGHT / 2)) - vector.translate(hour_mark, 0, 2) - vector.draw(hour_mark) - - angle_second = second * 6 - second_hand_length = int(HEIGHT / 2) - int(HEIGHT / 8) - second_hand = Polygon((-2, -second_hand_length), (-2, int(HEIGHT / 8)), (2, int(HEIGHT / 8)), (2, -second_hand_length)) - vector.rotate(second_hand, angle_second, 0, 0) - vector.translate(second_hand, int(WIDTH / 2), int(HEIGHT / 2) + 5) + t.rotate(360 / 60.0, (WIDTH / 2, HEIGHT / 2)) + if a % 5 == 0: + vector.draw(hour_mark) + else: + vector.draw(tick_mark) + t.reset() angle_minute = minute * 6 angle_minute += second / 10.0 - minute_hand_length = int(HEIGHT / 2) - int(HEIGHT / 24) - minute_hand = Polygon((-5, -minute_hand_length), (-10, int(HEIGHT / 16)), (10, int(HEIGHT / 16)), (5, -minute_hand_length)) - vector.rotate(minute_hand, angle_minute, 0, 0) - vector.translate(minute_hand, int(WIDTH / 2), int(HEIGHT / 2) + 5) + t.translate(WIDTH / 2, HEIGHT / 2 + 5) + t.rotate(angle_minute, (0, 0)) + vector.draw(minute_hand) + t.reset() angle_hour = (hour % 12) * 30 angle_hour += minute / 2 - hour_hand_length = int(HEIGHT / 2) - int(HEIGHT / 8) - hour_hand = Polygon((-5, -hour_hand_length), (-10, int(HEIGHT / 16)), (10, int(HEIGHT / 16)), (5, -hour_hand_length)) - vector.rotate(hour_hand, angle_hour, 0, 0) - vector.translate(hour_hand, int(WIDTH / 2), int(HEIGHT / 2) + 5) - - display.set_pen(GREY) - - vector.draw(minute_hand) + t.translate(WIDTH / 2, HEIGHT / 2 + 5) + t.rotate(angle_hour, (0, 0)) vector.draw(hour_hand) + + t.reset() + t.translate(WIDTH / 2, HEIGHT / 2 + 5) + t.rotate(second * 6, (0, 0)) vector.draw(second_hand) display.set_pen(BLACK) + t.reset() for a in range(60): - tick_mark = Rectangle(int(WIDTH / 2) - 3, 10, 6, int(HEIGHT / 48)) - vector.rotate(tick_mark, 360 / 60.0 * a, int(WIDTH / 2), int(HEIGHT / 2)) - vector.draw(tick_mark) + t.rotate(360 / 60.0, (WIDTH / 2, HEIGHT / 2)) + if a % 5 == 0: + vector.draw(hour_mark) + else: + vector.draw(tick_mark) - for a in range(12): - hour_mark = Rectangle(int(WIDTH / 2) - 5, 10, 10, int(HEIGHT / 10)) - vector.rotate(hour_mark, 360 / 12.0 * a, int(WIDTH / 2), int(HEIGHT / 2)) - vector.draw(hour_mark) - - vector.translate(minute_hand, 0, -5) - vector.translate(hour_hand, 0, -5) + t.reset() + angle_minute = minute * 6 + angle_minute += second / 10.0 + t.translate(WIDTH / 2, HEIGHT / 2) + t.rotate(angle_minute, (0, 0)) vector.draw(minute_hand) + + t.reset() + angle_hour = (hour % 12) * 30 + angle_hour += minute / 2 + t.translate(WIDTH / 2, HEIGHT / 2) + t.rotate(angle_hour, (0, 0)) vector.draw(hour_hand) display.set_pen(RED) - vector.translate(second_hand, 0, -5) + + t.reset() + t.translate(WIDTH / 2, HEIGHT / 2) + t.rotate(second * 6, (0, 0)) vector.draw(second_hand) + + vector.set_transform(None) vector.draw(hub) display.update() + mem = gc.mem_free() gc.collect() + used = gc.mem_free() - mem t_end = time.ticks_ms() - print(f"Took {t_end - t_start}ms") + print(f"Took {t_end - t_start}ms, mem free: {gc.mem_free()} {used}") diff --git a/micropython/modules/picovector/picovector.c b/micropython/modules/picovector/picovector.c index f5921ea20..527482714 100644 --- a/micropython/modules/picovector/picovector.c +++ b/micropython/modules/picovector/picovector.c @@ -3,64 +3,77 @@ /* Polygon */ static MP_DEFINE_CONST_FUN_OBJ_1(POLYGON__del__obj, POLYGON__del__); + +// Transformations +//static MP_DEFINE_CONST_FUN_OBJ_KW(POLYGON_rotate_obj, 3, POLYGON_rotate); +//static MP_DEFINE_CONST_FUN_OBJ_KW(POLYGON_translate_obj, 4, POLYGON_translate); + +// Utility functions static MP_DEFINE_CONST_FUN_OBJ_1(POLYGON_centroid_obj, POLYGON_centroid); static MP_DEFINE_CONST_FUN_OBJ_1(POLYGON_bounds_obj, POLYGON_bounds); +static MP_DEFINE_CONST_FUN_OBJ_2(POLYGON_transform_obj, POLYGON_transform); + +// Primitives +static MP_DEFINE_CONST_FUN_OBJ_KW(POLYGON_rectangle_obj, 5, POLYGON_rectangle); +static MP_DEFINE_CONST_FUN_OBJ_VAR(POLYGON_path_obj, 4, POLYGON_path); +static MP_DEFINE_CONST_FUN_OBJ_KW(POLYGON_regular_obj, 5, POLYGON_regular); +static MP_DEFINE_CONST_FUN_OBJ_KW(POLYGON_circle_obj, 4, POLYGON_circle); +static MP_DEFINE_CONST_FUN_OBJ_KW(POLYGON_arc_obj, 6, POLYGON_arc); static const mp_rom_map_elem_t POLYGON_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&POLYGON__del__obj) }, + + // Transformations + //{ MP_ROM_QSTR(MP_QSTR_rotate), MP_ROM_PTR(&POLYGON_rotate_obj) }, + //{ MP_ROM_QSTR(MP_QSTR_translate), MP_ROM_PTR(&POLYGON_translate_obj) }, + + // Utility functions { MP_ROM_QSTR(MP_QSTR_centroid), MP_ROM_PTR(&POLYGON_centroid_obj) }, { MP_ROM_QSTR(MP_QSTR_bounds), MP_ROM_PTR(&POLYGON_bounds_obj) }, + { MP_ROM_QSTR(MP_QSTR_transform), MP_ROM_PTR(&POLYGON_transform_obj) }, + + // Primitives + { MP_ROM_QSTR(MP_QSTR_rectangle), MP_ROM_PTR(&POLYGON_rectangle_obj) }, + { MP_ROM_QSTR(MP_QSTR_regular), MP_ROM_PTR(&POLYGON_regular_obj) }, + { MP_ROM_QSTR(MP_QSTR_path), MP_ROM_PTR(&POLYGON_path_obj) }, + { MP_ROM_QSTR(MP_QSTR_circle), MP_ROM_PTR(&POLYGON_circle_obj) }, + { MP_ROM_QSTR(MP_QSTR_arc), MP_ROM_PTR(&POLYGON_arc_obj) }, }; static MP_DEFINE_CONST_DICT(POLYGON_locals_dict, POLYGON_locals_dict_table); -#ifdef MP_DEFINE_CONST_OBJ_TYPE MP_DEFINE_CONST_OBJ_TYPE( POLYGON_type, MP_QSTR_polygon, MP_TYPE_FLAG_NONE, make_new, POLYGON_make_new, print, POLYGON_print, - iter, PATH_getiter, - locals_dict, (mp_obj_dict_t*)&POLYGON_locals_dict -); -MP_DEFINE_CONST_OBJ_TYPE( - REGULAR_POLYGON_type, - MP_QSTR_regular_polygon, - MP_TYPE_FLAG_NONE, - make_new, REGULAR_POLYGON_make_new, + iter, POLYGON_getiter, locals_dict, (mp_obj_dict_t*)&POLYGON_locals_dict ); + +/* Transform */ + +static MP_DEFINE_CONST_FUN_OBJ_3(TRANSFORM_rotate_obj, TRANSFORM_rotate); +static MP_DEFINE_CONST_FUN_OBJ_3(TRANSFORM_translate_obj, TRANSFORM_translate); +static MP_DEFINE_CONST_FUN_OBJ_1(TRANSFORM_reset_obj, TRANSFORM_reset); + +static const mp_rom_map_elem_t TRANSFORM_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_rotate), MP_ROM_PTR(&TRANSFORM_rotate_obj) }, + { MP_ROM_QSTR(MP_QSTR_translate), MP_ROM_PTR(&TRANSFORM_translate_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&TRANSFORM_reset_obj) }, +}; + +static MP_DEFINE_CONST_DICT(TRANSFORM_locals_dict, TRANSFORM_locals_dict_table); + MP_DEFINE_CONST_OBJ_TYPE( - RECTANGLE_type, - MP_QSTR_rectangle, + TRANSFORM_type, + MP_QSTR_Transform, MP_TYPE_FLAG_NONE, - make_new, RECTANGLE_make_new, - locals_dict, (mp_obj_dict_t*)&POLYGON_locals_dict + make_new, TRANSFORM_make_new, + locals_dict, (mp_obj_dict_t*)&TRANSFORM_locals_dict ); -#else -const mp_obj_type_t POLYGON_type = { - { &mp_type_type }, - .name = MP_QSTR_polygon, - .make_new = POLYGON_make_new, - .print = POLYGON_print, - .iter = PATH_getiter, - .locals_dict = (mp_obj_dict_t*)&POLYGON_locals_dict, -}; -const mp_obj_type_t REGULAR_POLYGON_type = { - { &mp_type_type }, - .name = MP_QSTR_regular_polygon, - .make_new = REGULAR_POLYGON_make_new, - .locals_dict = (mp_obj_dict_t*)&POLYGON_locals_dict, -}; -const mp_obj_type_t RECTANGLE_type = { - { &mp_type_type }, - .name = MP_QSTR_rectangle, - .make_new = RECTANGLE_make_new, - .locals_dict = (mp_obj_dict_t*)&POLYGON_locals_dict, -}; -#endif /* PicoVector */ @@ -68,25 +81,24 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(VECTOR_text_obj, 4, VECTOR_text); static MP_DEFINE_CONST_FUN_OBJ_3(VECTOR_set_font_obj, VECTOR_set_font); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_size_obj, VECTOR_set_font_size); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_antialiasing_obj, VECTOR_set_antialiasing); +static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_transform_obj, VECTOR_set_transform); +static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_clip_obj, VECTOR_set_clip); -static MP_DEFINE_CONST_FUN_OBJ_KW(VECTOR_draw_obj, 2, VECTOR_draw); -static MP_DEFINE_CONST_FUN_OBJ_KW(VECTOR_rotate_obj, 3, VECTOR_rotate); -static MP_DEFINE_CONST_FUN_OBJ_KW(VECTOR_translate_obj, 4, VECTOR_translate); +static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_draw_obj, VECTOR_draw); static const mp_rom_map_elem_t VECTOR_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_set_font), MP_ROM_PTR(&VECTOR_set_font_obj) }, { MP_ROM_QSTR(MP_QSTR_set_font_size), MP_ROM_PTR(&VECTOR_set_font_size_obj) }, { MP_ROM_QSTR(MP_QSTR_set_antialiasing), MP_ROM_PTR(&VECTOR_set_antialiasing_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_transform), MP_ROM_PTR(&VECTOR_set_transform_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_clip), MP_ROM_PTR(&VECTOR_set_clip_obj) }, { MP_ROM_QSTR(MP_QSTR_text), MP_ROM_PTR(&VECTOR_text_obj) }, { MP_ROM_QSTR(MP_QSTR_draw), MP_ROM_PTR(&VECTOR_draw_obj) }, - { MP_ROM_QSTR(MP_QSTR_rotate), MP_ROM_PTR(&VECTOR_rotate_obj) }, - { MP_ROM_QSTR(MP_QSTR_translate), MP_ROM_PTR(&VECTOR_translate_obj) }, }; static MP_DEFINE_CONST_DICT(VECTOR_locals_dict, VECTOR_locals_dict_table); -#ifdef MP_DEFINE_CONST_OBJ_TYPE MP_DEFINE_CONST_OBJ_TYPE( VECTOR_type, MP_QSTR_picovector, @@ -94,14 +106,6 @@ MP_DEFINE_CONST_OBJ_TYPE( make_new, VECTOR_make_new, locals_dict, (mp_obj_dict_t*)&VECTOR_locals_dict ); -#else -const mp_obj_type_t VECTOR_type = { - { &mp_type_type }, - .name = MP_QSTR_picovector, - .make_new = VECTOR_make_new, - .locals_dict = (mp_obj_dict_t*)&VECTOR_locals_dict, -}; -#endif /* Module */ @@ -109,8 +113,7 @@ static const mp_map_elem_t VECTOR_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_picovector) }, { MP_OBJ_NEW_QSTR(MP_QSTR_PicoVector), (mp_obj_t)&VECTOR_type }, { MP_OBJ_NEW_QSTR(MP_QSTR_Polygon), (mp_obj_t)&POLYGON_type }, - { MP_OBJ_NEW_QSTR(MP_QSTR_RegularPolygon), (mp_obj_t)®ULAR_POLYGON_type }, - { MP_OBJ_NEW_QSTR(MP_QSTR_Rectangle), (mp_obj_t)&RECTANGLE_type }, + { MP_OBJ_NEW_QSTR(MP_QSTR_Transform), (mp_obj_t)&TRANSFORM_type }, { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_NONE), MP_ROM_INT(0) }, { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_X4), MP_ROM_INT(1) }, { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_X16), MP_ROM_INT(2) }, @@ -125,8 +128,4 @@ const mp_obj_module_t VECTOR_user_cmodule = { .globals = (mp_obj_dict_t*)&mp_module_VECTOR_globals, }; -#if MICROPY_VERSION <= 70144 -MP_REGISTER_MODULE(MP_QSTR_picovector, VECTOR_user_cmodule, MODULE_PICOVECTOR_ENABLED); -#else -MP_REGISTER_MODULE(MP_QSTR_picovector, VECTOR_user_cmodule); -#endif \ No newline at end of file +MP_REGISTER_MODULE(MP_QSTR_picovector, VECTOR_user_cmodule); \ No newline at end of file diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index 8da08e4ff..6489f7ccf 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -25,10 +25,15 @@ typedef struct _VECTOR_obj_t { PicoVector *vector; } _VECTOR_obj_t; -typedef struct _PATH_obj_t { +typedef struct _TRANSFORM_obj_t { mp_obj_base_t base; - pp_path_t *path; -} _PATH_obj_t; + pp_mat3_t transform; +} _TRANSFORM_obj_t; + +typedef struct _POLY_obj_t { + mp_obj_base_t base; + pp_poly_t *poly; +} _POLY_obj_t; void __printf_debug_flush() { for(auto i = 0u; i < 10; i++) { @@ -54,20 +59,22 @@ void af_debug(const char *fmt, ...) { 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); + //void *addr = m_tracked_calloc(sizeof(uint8_t), size); + void *addr = m_malloc(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; + return m_realloc(p, size); } void af_free(void *p) { //mp_printf(&mp_plat_print, "af_free\n"); //__printf_debug_flush(); - m_tracked_free(p); + //m_tracked_free(p); + m_free(p); } void* fileio_open(const char *filename) { @@ -157,123 +164,210 @@ static const std::string_view mp_obj_to_string_r(const mp_obj_t &obj) { /* POLYGON */ -mp_obj_t RECTANGLE_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_x, ARG_y, ARG_w, ARG_h }; +mp_obj_t POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + _POLY_obj_t *self = mp_obj_malloc_with_finaliser(_POLY_obj_t, &POLYGON_type); + self->poly = pp_poly_new(); + return self; +} + +mp_obj_t POLYGON__del__(mp_obj_t self_in) { + _POLY_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLY_obj_t); + pp_poly_free(self->poly); + return mp_const_none; +} + +mp_obj_t POLYGON_path(size_t n_args, const mp_obj_t *all_args) { + _POLY_obj_t *self = MP_OBJ_TO_PTR2(all_args[0], _POLY_obj_t); + + size_t num_points = n_args - 1; + const mp_obj_t *points = all_args + 1; + + pp_path_t *path = pp_poly_add_path(self->poly); + + if(num_points < 3) mp_raise_ValueError("Polygon: At least 3 points required."); + + for(auto i = 0u; i < num_points; i++) { + mp_obj_t c_obj = points[i]; + + if(!mp_obj_is_exact_type(c_obj, &mp_type_tuple)) mp_raise_ValueError("Not a tuple"); + + mp_obj_tuple_t *t_point = MP_OBJ_TO_PTR2(c_obj, mp_obj_tuple_t); + + if(t_point->len != 2) mp_raise_ValueError("Tuple must have X, Y"); + + pp_path_add_point(path, { + (picovector_point_type)mp_picovector_get_point_type(t_point->items[0]), + (picovector_point_type)mp_picovector_get_point_type(t_point->items[1]), + }); + } + + return mp_const_none; +} + +mp_obj_t POLYGON_rectangle(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_x, ARG_y, ARG_w, ARG_h, ARG_corners, ARG_stroke }; static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_w, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_h, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_corners, MP_ARG_OBJ, { .u_obj = mp_const_none }}, + { MP_QSTR_stroke, MP_ARG_OBJ, { .u_obj = mp_const_none }}, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - _PATH_obj_t *self = mp_obj_malloc_with_finaliser(_PATH_obj_t, &POLYGON_type); - self->path = (pp_path_t *)PP_MALLOC(sizeof(pp_path_t)); - self->path->storage = 4; - self->path->points = (pp_point_t *)PP_MALLOC(sizeof(pp_point_t) * self->path->storage); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _POLY_obj_t); picovector_point_type x = mp_picovector_get_point_type(args[ARG_x].u_obj); picovector_point_type y = mp_picovector_get_point_type(args[ARG_y].u_obj); picovector_point_type w = mp_picovector_get_point_type(args[ARG_w].u_obj); picovector_point_type h = mp_picovector_get_point_type(args[ARG_h].u_obj); + picovector_point_type s = args[ARG_stroke].u_obj == mp_const_none ? 0 : mp_picovector_get_point_type(args[ARG_stroke].u_obj); - pp_path_add_point(self->path, {picovector_point_type(x), picovector_point_type(y)}); - pp_path_add_point(self->path, {picovector_point_type(x + w), picovector_point_type(y)}); - pp_path_add_point(self->path, {picovector_point_type(x + w), picovector_point_type(y + h)}); - pp_path_add_point(self->path, {picovector_point_type(x), picovector_point_type(y + h)}); + picovector_point_type r1 = 0; + picovector_point_type r2 = 0; + picovector_point_type r3 = 0; + picovector_point_type r4 = 0; + + if(mp_obj_is_exact_type(args[ARG_corners].u_obj, &mp_type_tuple)){ + mp_obj_tuple_t *t_corners = MP_OBJ_TO_PTR2(args[ARG_corners].u_obj, mp_obj_tuple_t); + + if(t_corners->len != 4) mp_raise_ValueError("Corners must have r1, r2, r3, r4"); + + r1 = mp_picovector_get_point_type(t_corners->items[0]); + r2 = mp_picovector_get_point_type(t_corners->items[1]); + r3 = mp_picovector_get_point_type(t_corners->items[2]); + r4 = mp_picovector_get_point_type(t_corners->items[3]); + } + + pp_poly_merge(self->poly, ppp_rect({ + x, y, w, h, + s, + r1, r2, r3, r4 + })); return self; } -mp_obj_t REGULAR_POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_x, ARG_y, ARG_sides, ARG_radius, ARG_rotation }; +mp_obj_t POLYGON_regular(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_x, ARG_y, ARG_sides, ARG_radius, ARG_stroke }; static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_sides, MP_ARG_REQUIRED | MP_ARG_INT }, { MP_QSTR_radius, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_rotation, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_sides, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_stroke, MP_ARG_OBJ, { .u_obj = mp_const_none }}, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - _PATH_obj_t *self = mp_obj_malloc_with_finaliser(_PATH_obj_t, &POLYGON_type); + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - Point origin(args[ARG_x].u_int, args[ARG_y].u_int); - unsigned int sides = args[ARG_sides].u_int; - float radius = mp_obj_get_float(args[ARG_radius].u_obj); - float rotation = 0.0f; - if (args[ARG_rotation].u_obj != mp_const_none) { - rotation = mp_obj_get_float(args[ARG_rotation].u_obj); - rotation *= (M_PI / 180.0f); - } - picovector_point_type o_x = mp_picovector_get_point_type(args[ARG_x].u_obj); - picovector_point_type o_y = mp_picovector_get_point_type(args[ARG_y].u_obj); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _POLY_obj_t); - float angle = (360.0f / sides) * (M_PI / 180.0f); + picovector_point_type x = mp_picovector_get_point_type(args[ARG_x].u_obj); + picovector_point_type y = mp_picovector_get_point_type(args[ARG_y].u_obj); + picovector_point_type r = mp_picovector_get_point_type(args[ARG_radius].u_obj); + int e = args[ARG_sides].u_int; + picovector_point_type s = args[ARG_stroke].u_obj == mp_const_none ? 0 : mp_picovector_get_point_type(args[ARG_stroke].u_obj); - self->path = (pp_path_t *)PP_MALLOC(sizeof(pp_path_t)); - self->path->storage = sides; - self->path->points = (pp_point_t *)PP_MALLOC(sizeof(pp_point_t) * self->path->storage); - - for(auto s = 0u; s < sides; s++) { - float current_angle = angle * s + rotation; - pp_path_add_point(self->path, { - (picovector_point_type)(cos(current_angle) * radius) + o_x, - (picovector_point_type)(sin(current_angle) * radius) + o_y - }); - } + pp_poly_merge(self->poly, ppp_regular({ + x, y, + r, + e, + s + })); return self; } -mp_obj_t POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - _PATH_obj_t *self = mp_obj_malloc_with_finaliser(_PATH_obj_t, &POLYGON_type); +mp_obj_t POLYGON_circle(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_x, ARG_y, ARG_radius, ARG_stroke }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_radius, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_stroke, MP_ARG_OBJ, { .u_obj = mp_const_none }}, + }; - size_t num_points = n_args; - const mp_obj_t *points = all_args; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - self->path = (pp_path_t *)PP_MALLOC(sizeof(pp_path_t)); - self->path->storage = num_points; - self->path->points = (pp_point_t *)PP_MALLOC(sizeof(pp_point_t) * self->path->storage); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _POLY_obj_t); - if(num_points < 3) mp_raise_ValueError("Polygon: At least 3 points required."); + picovector_point_type x = mp_picovector_get_point_type(args[ARG_x].u_obj); + picovector_point_type y = mp_picovector_get_point_type(args[ARG_y].u_obj); + picovector_point_type r = mp_picovector_get_point_type(args[ARG_radius].u_obj); + picovector_point_type s = args[ARG_stroke].u_obj == mp_const_none ? 0 : mp_picovector_get_point_type(args[ARG_stroke].u_obj); - for(auto i = 0u; i < num_points; i++) { - mp_obj_t c_obj = points[i]; + pp_poly_merge(self->poly, ppp_circle({ + x, y, + r, + s + })); - if(!mp_obj_is_exact_type(c_obj, &mp_type_tuple)) mp_raise_ValueError("Not a tuple"); + return self; +} - mp_obj_tuple_t *t_point = MP_OBJ_TO_PTR2(c_obj, mp_obj_tuple_t); +mp_obj_t POLYGON_arc(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_x, ARG_y, ARG_radius, ARG_from, ARG_to, ARG_stroke }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_radius, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_from, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_to, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_stroke, MP_ARG_OBJ, { .u_obj = mp_const_none }}, + }; - if(t_point->len != 2) mp_raise_ValueError("Tuple must have X, Y"); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - pp_path_add_point(self->path, { - (picovector_point_type)mp_picovector_get_point_type(t_point->items[0]), - (picovector_point_type)mp_picovector_get_point_type(t_point->items[1]), - }); - } + _POLY_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _POLY_obj_t); + + picovector_point_type x = mp_picovector_get_point_type(args[ARG_x].u_obj); + picovector_point_type y = mp_picovector_get_point_type(args[ARG_y].u_obj); + picovector_point_type r = mp_picovector_get_point_type(args[ARG_radius].u_obj); + picovector_point_type f = mp_picovector_get_point_type(args[ARG_from].u_obj); + picovector_point_type t = mp_picovector_get_point_type(args[ARG_to].u_obj); + picovector_point_type s = args[ARG_stroke].u_obj == mp_const_none ? 0 : mp_picovector_get_point_type(args[ARG_stroke].u_obj); + + pp_poly_merge(self->poly, ppp_arc({ + x, y, + r, + s, + f, + t + })); return self; } +// Utility functions + mp_obj_t POLYGON_centroid(mp_obj_t self_in) { - _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLY_obj_t); PP_COORD_TYPE sum_x = (PP_COORD_TYPE)0; PP_COORD_TYPE sum_y = (PP_COORD_TYPE)0; - for(auto i = 0; i < self->path->count; i++) { - sum_x += self->path->points[i].x; - sum_y += self->path->points[i].y; + // TODO: Maybe include in pretty-poly? + // Might need to handle multiple paths + pp_path_t *path = self->poly->paths; + + for(auto i = 0; i < path->count; i++) { + sum_x += path->points[i].x; + sum_y += path->points[i].y; } - sum_x /= (float)self->path->count; - sum_y /= (float)self->path->count; + sum_x /= (float)path->count; + sum_y /= (float)path->count; mp_obj_t tuple[2]; tuple[0] = mp_picovector_set_point_type((int)(sum_x)); @@ -283,9 +377,9 @@ mp_obj_t POLYGON_centroid(mp_obj_t self_in) { } mp_obj_t POLYGON_bounds(mp_obj_t self_in) { - _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLY_obj_t); - pp_rect_t bounds = pp_path_bounds(self->path); + pp_rect_t bounds = pp_poly_bounds(self->poly); mp_obj_t tuple[4]; tuple[0] = mp_picovector_set_point_type((int)(bounds.x)); @@ -296,65 +390,129 @@ mp_obj_t POLYGON_bounds(mp_obj_t self_in) { return mp_obj_new_tuple(4, tuple); } -void POLYGON_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - (void)kind; - _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); +void _pp_path_transform(pp_path_t *path, pp_mat3_t *transform) { + for (int i = 0; i < path->count; i++) { + path->points[i] = pp_point_transform(&path->points[i], transform); + } +} - pp_rect_t bounds = pp_path_bounds(self->path); +void _pp_poly_transform(pp_poly_t *poly, pp_mat3_t *transform) { + pp_path_t *path = poly->paths; - mp_print_str(print, "Polygon(points = "); - mp_obj_print_helper(print, mp_picovector_set_point_type(self->path->count), PRINT_REPR); - mp_print_str(print, ", bounds = "); - mp_obj_print_helper(print, mp_picovector_set_point_type(bounds.x), PRINT_REPR); - mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_picovector_set_point_type(bounds.y), PRINT_REPR); - mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_picovector_set_point_type(bounds.w), PRINT_REPR); - mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_picovector_set_point_type(bounds.h), PRINT_REPR); - mp_print_str(print, ")"); + while(path) { + _pp_path_transform(path, transform); + path = path->next; + } } -mp_obj_t POLYGON__del__(mp_obj_t self_in) { - _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); - PP_FREE(self->path->points); - PP_FREE(self->path); - // TODO: Do we actually need to free anything here, if it's on GC heap it should get collected +mp_obj_t POLYGON_transform(mp_obj_t self_in, mp_obj_t transform_in) { + _POLY_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLY_obj_t); + + if (!MP_OBJ_IS_TYPE(transform_in, &TRANSFORM_type)) mp_raise_ValueError("Transform required"); + _TRANSFORM_obj_t *transform = (_TRANSFORM_obj_t *)MP_OBJ_TO_PTR(transform_in); + + _pp_poly_transform(self->poly, &transform->transform); + return mp_const_none; } +void POLYGON_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + _POLY_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLY_obj_t); + (void)self; + + // TODO: Make print better + mp_print_str(print, "Polygon();"); +} + typedef struct _mp_obj_polygon_it_t { mp_obj_base_t base; mp_fun_1_t iternext; mp_obj_t polygon; - int cur; + pp_path_t *cur; } mp_obj_polygon_it_t; -static mp_obj_t py_path_it_iternext(mp_obj_t self_in) { +static mp_obj_t POLYGON_it_iternext(mp_obj_t self_in) { mp_obj_polygon_it_t *self = MP_OBJ_TO_PTR2(self_in, mp_obj_polygon_it_t); - _PATH_obj_t *path = MP_OBJ_TO_PTR2(self->polygon, _PATH_obj_t); + //_POLY_obj_t *poly = MP_OBJ_TO_PTR2(self->polygon, _POLY_obj_t); //mp_printf(&mp_plat_print, "points: %d, current: %d\n", polygon->contour.count, self->cur); - if(self->cur >= path->path->count) return MP_OBJ_STOP_ITERATION; + if(!self->cur) return MP_OBJ_STOP_ITERATION; - mp_obj_t tuple[2]; - tuple[0] = mp_picovector_set_point_type((int)(path->path->points[self->cur].x)); - tuple[1] = mp_picovector_set_point_type((int)(path->path->points[self->cur].y)); + mp_obj_t tuple[self->cur->count]; + for (auto i = 0; i < self->cur->count; i++) { + mp_obj_t t_point[2] = { + mp_picovector_set_point_type((int)(self->cur->points[i].x)), + mp_picovector_set_point_type((int)(self->cur->points[i].y)) + }; + tuple[i] = mp_obj_new_tuple(2, t_point); + } - self->cur++; - return mp_obj_new_tuple(2, tuple); + self->cur = self->cur->next; + return mp_obj_new_tuple(self->cur->count, tuple); } -mp_obj_t PATH_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { +mp_obj_t POLYGON_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { mp_obj_polygon_it_t *o = (mp_obj_polygon_it_t *)iter_buf; o->base.type = &mp_type_polymorph_iter; - o->iternext = py_path_it_iternext; + o->iternext = POLYGON_it_iternext; o->polygon = o_in; - o->cur = 0; + o->cur = MP_OBJ_TO_PTR2(o_in, _POLY_obj_t)->poly->paths; return MP_OBJ_FROM_PTR(o); } +/* TRANSFORM */ + +mp_obj_t TRANSFORM_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + _TRANSFORM_obj_t *self = m_new_obj(_TRANSFORM_obj_t); + self->base.type = &TRANSFORM_type; + + self->transform = pp_mat3_identity(); + + return self; +} + +mp_obj_t TRANSFORM_rotate(mp_obj_t self_in, mp_obj_t angle_in, mp_obj_t origin_in) { + _TRANSFORM_obj_t *transform = MP_OBJ_TO_PTR2(self_in, _TRANSFORM_obj_t); + + float angle = mp_obj_get_float(angle_in); + + if(mp_obj_is_exact_type(origin_in, &mp_type_tuple)) { + mp_obj_tuple_t *t_origin = MP_OBJ_TO_PTR2(origin_in, mp_obj_tuple_t); + + if(t_origin->len != 2) mp_raise_ValueError("Origin Tuple must have X, Y"); + + picovector_point_type x = mp_picovector_get_point_type(t_origin->items[0]); + picovector_point_type y = mp_picovector_get_point_type(t_origin->items[1]); + + pp_mat3_translate(&transform->transform, x, y); + pp_mat3_rotate(&transform->transform, angle); + pp_mat3_translate(&transform->transform, -x, -y); + } else { + pp_mat3_rotate(&transform->transform, angle); + } + + return mp_const_none; +} + +mp_obj_t TRANSFORM_translate(mp_obj_t self_in, mp_obj_t x_in, mp_obj_t y_in) { + _TRANSFORM_obj_t *transform = MP_OBJ_TO_PTR2(self_in, _TRANSFORM_obj_t); + + picovector_point_type o_x = mp_picovector_get_point_type(x_in); + picovector_point_type o_y = mp_picovector_get_point_type(y_in); + + pp_mat3_translate(&transform->transform, o_x, o_y); + + return mp_const_none; +} + +mp_obj_t TRANSFORM_reset(mp_obj_t self_in) { + _TRANSFORM_obj_t *transform = MP_OBJ_TO_PTR2(self_in, _TRANSFORM_obj_t); + transform->transform = pp_mat3_identity(); + return mp_const_none; +} + /* VECTOR */ mp_obj_t VECTOR_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { @@ -383,6 +541,24 @@ mp_obj_t VECTOR_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, return self; } +mp_obj_t VECTOR_set_transform(mp_obj_t self_in, mp_obj_t transform_in) { + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); + (void)self; + + if(transform_in == mp_const_none) { + pp_mat3_t* old = pp_transform(NULL); + (void)old; // TODO: Return old transform? + } else if MP_OBJ_IS_TYPE(transform_in, &TRANSFORM_type) { + _TRANSFORM_obj_t *transform = (_TRANSFORM_obj_t *)MP_OBJ_TO_PTR(transform_in); + pp_mat3_t* old = pp_transform(&transform->transform); + (void)old; + } else { + // TODO: ValueError? + } + + return mp_const_none; +} + mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size) { _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); (void)self; @@ -413,6 +589,31 @@ mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size) { return mp_const_none; } +mp_obj_t VECTOR_set_clip(mp_obj_t self_in, mp_obj_t clip_in) { + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); + (void)self; + + picovector_point_type x = self->vector->graphics->bounds.x; + picovector_point_type y = self->vector->graphics->bounds.y; + picovector_point_type w = self->vector->graphics->bounds.w; + picovector_point_type h = self->vector->graphics->bounds.h; + + if(mp_obj_is_exact_type(clip_in, &mp_type_tuple)){ + mp_obj_tuple_t *t_clip = MP_OBJ_TO_PTR2(clip_in, mp_obj_tuple_t); + + if(t_clip->len != 4) mp_raise_ValueError("Clip must have x, y, w, h"); + + x = mp_picovector_get_point_type(t_clip->items[0]); + y = mp_picovector_get_point_type(t_clip->items[1]); + w = mp_picovector_get_point_type(t_clip->items[2]); + h = mp_picovector_get_point_type(t_clip->items[3]); + } + + pp_clip(x, y, w, h); + + return mp_const_none; +} + mp_obj_t VECTOR_set_antialiasing(mp_obj_t self_in, mp_obj_t aa) { _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); @@ -465,94 +666,15 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) return mp_const_none; } -mp_obj_t VECTOR_rotate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_self, ARG_polygon, ARG_angle, ARG_origin_x, ARG_origin_y }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_polygon, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_angle, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_origin_x, MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_origin_y, MP_ARG_INT, {.u_int = 0} } - }; - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _VECTOR_obj_t); - - if(!MP_OBJ_IS_TYPE(args[ARG_polygon].u_obj, &POLYGON_type)) mp_raise_TypeError("rotate: polygon required"); - - _PATH_obj_t *poly = MP_OBJ_TO_PTR2(args[ARG_polygon].u_obj, _PATH_obj_t); - - pp_point_t origin = {(PP_COORD_TYPE)args[ARG_origin_x].u_int, (PP_COORD_TYPE)args[ARG_origin_y].u_int}; - - float angle = mp_obj_get_float(args[ARG_angle].u_obj); - - self->vector->rotate(poly->path, origin, angle); - - return mp_const_none; -} - -mp_obj_t VECTOR_translate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_self, ARG_polygon, ARG_x, ARG_y }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_polygon, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_x, MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_y, MP_ARG_INT, {.u_int = 0} } - }; - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _VECTOR_obj_t); - - if(!MP_OBJ_IS_TYPE(args[ARG_polygon].u_obj, &POLYGON_type)) mp_raise_TypeError("rotate: polygon required"); - - _PATH_obj_t *poly = MP_OBJ_TO_PTR2(args[ARG_polygon].u_obj, _PATH_obj_t); - - pp_point_t translate = {(PP_COORD_TYPE)args[ARG_x].u_int, (PP_COORD_TYPE)args[ARG_y].u_int}; - - self->vector->translate(poly->path, translate); - - return mp_const_none; -} - -mp_obj_t VECTOR_draw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - - size_t num_polygons = n_args - 1; - const mp_obj_t *polygons = pos_args + 1; - - _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(pos_args[0], _VECTOR_obj_t); - - if(num_polygons == 1) { - mp_obj_t poly_obj = polygons[0]; - - if(!MP_OBJ_IS_TYPE(poly_obj, &POLYGON_type)) mp_raise_TypeError("draw: Polygon required."); - - _PATH_obj_t *poly = MP_OBJ_TO_PTR2(poly_obj, _PATH_obj_t); - - self->vector->draw(poly->path); - - return mp_const_none; - } - - - pp_poly_t *group = pp_poly_new(); - - for(auto i = 0u; i < num_polygons; i++) { - pp_path_t *path = pp_poly_add_path(group); - mp_obj_t poly_obj = polygons[i]; - - if(!MP_OBJ_IS_TYPE(poly_obj, &POLYGON_type)) mp_raise_TypeError("draw: Polygon required."); +mp_obj_t VECTOR_draw(mp_obj_t self_in, mp_obj_t poly_in) { + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); + (void)self; - _PATH_obj_t *poly = MP_OBJ_TO_PTR2(poly_obj, _PATH_obj_t); - pp_path_add_points(path, poly->path->points, poly->path->count); - } + if(!MP_OBJ_IS_TYPE(poly_in, &POLYGON_type)) mp_raise_TypeError("draw: Polygon required."); - self->vector->draw(group); + _POLY_obj_t *poly = MP_OBJ_TO_PTR2(poly_in, _POLY_obj_t); - pp_poly_free(group); + pp_render(poly->poly); return mp_const_none; } diff --git a/micropython/modules/picovector/picovector.h b/micropython/modules/picovector/picovector.h index 547c59a1e..2bb0e74a9 100644 --- a/micropython/modules/picovector/picovector.h +++ b/micropython/modules/picovector/picovector.h @@ -3,26 +3,42 @@ extern const mp_obj_type_t VECTOR_type; extern const mp_obj_type_t POLYGON_type; -extern const mp_obj_type_t REGULAR_POLYGON_type; -extern const mp_obj_type_t RECTANGLE_type; +extern const mp_obj_type_t TRANSFORM_type; + +/* Polygon */ extern mp_obj_t POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); -extern mp_obj_t REGULAR_POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); -extern mp_obj_t RECTANGLE_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); +extern mp_obj_t POLYGON_path(size_t n_args, const mp_obj_t *all_args); +extern mp_obj_t POLYGON_regular(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t POLYGON_rectangle(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t POLYGON_circle(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t POLYGON_arc(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); + extern void POLYGON_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind); extern mp_obj_t POLYGON_centroid(mp_obj_t self_in); extern mp_obj_t POLYGON_bounds(mp_obj_t self_in); -extern mp_obj_t PATH_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf); - +extern mp_obj_t POLYGON_transform(mp_obj_t self_in, mp_obj_t transform_in); +extern mp_obj_t POLYGON_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf); extern mp_obj_t POLYGON__del__(mp_obj_t self_in); +/* Transform */ + +extern mp_obj_t TRANSFORM_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); +extern mp_obj_t TRANSFORM_rotate(mp_obj_t self_in, mp_obj_t angle_in, mp_obj_t origin_in); +extern mp_obj_t TRANSFORM_translate(mp_obj_t self_in, mp_obj_t x_in, mp_obj_t y_in); +extern mp_obj_t TRANSFORM_reset(mp_obj_t self_in); + +/* Vector */ + extern mp_obj_t VECTOR_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); extern mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size); extern mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size); extern mp_obj_t VECTOR_set_antialiasing(mp_obj_t self_in, mp_obj_t aa); +extern mp_obj_t VECTOR_set_transform(mp_obj_t self_in, mp_obj_t transform_in); +extern mp_obj_t VECTOR_set_clip(mp_obj_t self_in, mp_obj_t clip_in); -extern mp_obj_t VECTOR_draw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t VECTOR_draw(mp_obj_t self_in, mp_obj_t poly_in); extern mp_obj_t VECTOR_rotate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t VECTOR_translate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); \ No newline at end of file From adb9a1ee0bad30547e3788dcc9abc3ecee0ed075 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 8 Oct 2024 12:35:55 +0100 Subject: [PATCH 24/35] PicoGraphics: Add layer support to PicoVector tile renderer. --- .../pico_graphics_pen_rgb332.cpp | 21 ++++++++++----- .../pico_graphics_pen_rgb565.cpp | 26 +++++++++++++------ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp index 88fceba7d..b3136d6d5 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp @@ -99,7 +99,7 @@ namespace pimoroni { // Treat our void* frame_buffer as uint8_t uint8_t *src = (uint8_t *)frame_buffer; - if(this->layers > 1) { + if(this->layers > 1) { // The size of a single layer uint offset = this->bounds.w * this->bounds.h; @@ -148,14 +148,23 @@ namespace pimoroni { bool PicoGraphics_PenRGB332::render_tile(const Tile *tile) { for(int y = 0; y < tile->h; y++) { uint8_t *palpha = &tile->data[(y * tile->stride)]; - uint8_t *pdest = &((uint8_t *)frame_buffer)[tile->x + ((tile->y + y) * bounds.w)]; + + uint8_t *p_dest = &((uint8_t *)frame_buffer)[tile->x + ((tile->y + y) * bounds.w)]; + p_dest += this->layer_offset; + + uint8_t *p_layer0 = &((uint8_t *)frame_buffer)[tile->x + ((tile->y + y) * bounds.w)]; + + for(int x = 0; x < tile->w; x++) { uint8_t alpha = *palpha; - uint8_t dest = *pdest; + uint8_t dest = *p_dest; + if(dest == 0) { + dest = *p_layer0; + } // TODO: Try to alpha blend RGB332... somewhat? if(alpha == 255) { - *pdest = color; + *p_dest = color; }else if(alpha == 0) { }else{ // blend tha pixel @@ -172,10 +181,10 @@ namespace pimoroni { uint8_t b = ((sb * alpha) + (db * (255 - alpha))) >> 8; // recombine the channels - *pdest = (r << 5) | (g << 2) | (b); + *p_dest = (r << 5) | (g << 2) | (b); } - pdest++; + p_dest++; palpha++; } } diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp index 5e9d5f676..432767e2b 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp @@ -66,6 +66,7 @@ namespace pimoroni { if(!bounds.contains(p)) return; uint16_t *buf = (uint16_t *)frame_buffer; + buf += this->layer_offset; RGB565 blended = RGB(buf[p.y * bounds.w + p.x]).blend(RGB(color), a).to_rgb565(); @@ -96,14 +97,22 @@ namespace pimoroni { bool PicoGraphics_PenRGB565::render_tile(const Tile *tile) { for(int y = 0; y < tile->h; y++) { - uint8_t *palpha = &tile->data[(y * tile->stride)]; - uint16_t *pdest = &((uint16_t *)frame_buffer)[tile->x + ((tile->y + y) * bounds.w)]; + uint8_t *p_alpha = &tile->data[(y * tile->stride)]; + + uint16_t *p_dest = &((uint16_t *)frame_buffer)[tile->x + ((tile->y + y) * bounds.w)]; + p_dest += this->layer_offset; + + uint16_t *p_layer0 = &((uint16_t *)frame_buffer)[tile->x + ((tile->y + y) * bounds.w)]; + for(int x = 0; x < tile->w; x++) { - uint16_t dest = *pdest; - uint8_t alpha = *palpha; + uint16_t dest = *p_dest; + if(dest == 0) { + dest = *p_layer0; + } + uint8_t alpha = *p_alpha; if(alpha == 255) { - *pdest = color; + *p_dest = color; }else if(alpha == 0) { }else{ // blend tha pixel @@ -120,11 +129,12 @@ namespace pimoroni { uint8_t b = ((sb * alpha) + (db * (255 - alpha))) >> 8; // recombine the channels - *pdest = __builtin_bswap16((r << 11) | (g << 5) | (b)); + *p_dest = __builtin_bswap16((r << 11) | (g << 5) | (b)); } - pdest++; - palpha++; + p_layer0++; + p_dest++; + p_alpha++; } } From 352bd471179b935688c88ba8b6f2ca78bd023b5b Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 8 Oct 2024 15:24:08 +0100 Subject: [PATCH 25/35] PicoVector: Improve text rendering and control. --- libraries/pico_vector/alright-fonts.h | 14 ++++--- libraries/pico_vector/pico_vector.cpp | 2 +- libraries/pico_vector/pico_vector.hpp | 12 ++++++ micropython/modules/picovector/picovector.c | 8 ++++ micropython/modules/picovector/picovector.cpp | 38 ++++++++++++++++++- micropython/modules/picovector/picovector.h | 4 ++ 6 files changed, 70 insertions(+), 8 deletions(-) diff --git a/libraries/pico_vector/alright-fonts.h b/libraries/pico_vector/alright-fonts.h index 9e5a6ce9d..03166bd18 100644 --- a/libraries/pico_vector/alright-fonts.h +++ b/libraries/pico_vector/alright-fonts.h @@ -273,8 +273,7 @@ int get_max_line_width(af_face_t *face, const char *text, af_text_metrics_t *tm) return max_width; } - -void af_render(af_face_t *face, const char *text, af_text_metrics_t *tm) { +void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) { pp_mat3_t *old = pp_transform(NULL); float line_height = (tm->line_height * 128.0f) / 100.0f; @@ -290,8 +289,9 @@ void af_render(af_face_t *face, const char *text, af_text_metrics_t *tm) { caret.x = 0; caret.y = 0; + char *done = (char *)text + tlen; char *end = strchr(text, '\n'); - if (!end) end = (char *)text + strlen(text); + if (!end) end = done; while(true) { int line_width = get_line_width(face, text, tm); @@ -327,9 +327,9 @@ void af_render(af_face_t *face, const char *text, af_text_metrics_t *tm) { } text = end + 1; - if (*text == '\0') break; + if (*text == '\0' || text > done) break; end = strchr(text, '\n'); - if (!end) end = (char *)text + strlen(text); + if (!end) end = (char *)text + tlen; caret.x = 0; caret.y += line_height; @@ -340,6 +340,10 @@ void af_render(af_face_t *face, const char *text, af_text_metrics_t *tm) { pp_transform(old); } +void _af_render(af_face_t *face, const char *text, af_text_metrics_t *tm) { + af_render(face, text, strlen(text), tm); +} + pp_rect_t af_measure(af_face_t *face, const char *text, af_text_metrics_t *tm) { pp_rect_t result; bool first = true; diff --git a/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp index a783135c1..41d158826 100644 --- a/libraries/pico_vector/pico_vector.cpp +++ b/libraries/pico_vector/pico_vector.cpp @@ -65,7 +65,7 @@ namespace pimoroni { text_metrics.transform = t; - af_render(text_metrics.face, text.data(), &text_metrics); + af_render(text_metrics.face, text.data(), text.size(), &text_metrics); return caret; /* diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index 97a5233fe..c982dd0ea 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -67,6 +67,18 @@ namespace pimoroni { text_metrics.size = font_size; } + void set_font_word_spacing(unsigned int font_wordspacing) { + text_metrics.word_spacing = font_wordspacing; + } + + void set_font_letter_spacing(unsigned int font_letterspacing) { + text_metrics.letter_spacing = font_letterspacing; + } + + void set_font_line_height(unsigned int font_line_height) { + text_metrics.line_height = font_line_height; + } + bool set_font(std::string_view font_path, unsigned int font_size) { if(text_metrics.face) { af_free(text_metrics.face->glyphs); diff --git a/micropython/modules/picovector/picovector.c b/micropython/modules/picovector/picovector.c index 527482714..bc87a0dd5 100644 --- a/micropython/modules/picovector/picovector.c +++ b/micropython/modules/picovector/picovector.c @@ -57,11 +57,13 @@ MP_DEFINE_CONST_OBJ_TYPE( static MP_DEFINE_CONST_FUN_OBJ_3(TRANSFORM_rotate_obj, TRANSFORM_rotate); static MP_DEFINE_CONST_FUN_OBJ_3(TRANSFORM_translate_obj, TRANSFORM_translate); +static MP_DEFINE_CONST_FUN_OBJ_3(TRANSFORM_scale_obj, TRANSFORM_scale); static MP_DEFINE_CONST_FUN_OBJ_1(TRANSFORM_reset_obj, TRANSFORM_reset); static const mp_rom_map_elem_t TRANSFORM_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_rotate), MP_ROM_PTR(&TRANSFORM_rotate_obj) }, { MP_ROM_QSTR(MP_QSTR_translate), MP_ROM_PTR(&TRANSFORM_translate_obj) }, + { MP_ROM_QSTR(MP_QSTR_scale), MP_ROM_PTR(&TRANSFORM_scale_obj) }, { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&TRANSFORM_reset_obj) }, }; @@ -80,6 +82,9 @@ MP_DEFINE_CONST_OBJ_TYPE( static MP_DEFINE_CONST_FUN_OBJ_KW(VECTOR_text_obj, 4, VECTOR_text); static MP_DEFINE_CONST_FUN_OBJ_3(VECTOR_set_font_obj, VECTOR_set_font); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_size_obj, VECTOR_set_font_size); +static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_word_spacing_obj, VECTOR_set_font_word_spacing); +static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_letter_spacing_obj, VECTOR_set_font_letter_spacing); +static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_line_height_obj, VECTOR_set_font_line_height); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_antialiasing_obj, VECTOR_set_antialiasing); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_transform_obj, VECTOR_set_transform); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_clip_obj, VECTOR_set_clip); @@ -89,6 +94,9 @@ static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_draw_obj, VECTOR_draw); static const mp_rom_map_elem_t VECTOR_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_set_font), MP_ROM_PTR(&VECTOR_set_font_obj) }, { MP_ROM_QSTR(MP_QSTR_set_font_size), MP_ROM_PTR(&VECTOR_set_font_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_font_word_spacing), MP_ROM_PTR(&VECTOR_set_font_word_spacing_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_font_letter_spacing), MP_ROM_PTR(&VECTOR_set_font_letter_spacing_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_font_line_height), MP_ROM_PTR(&VECTOR_set_font_line_height_obj) }, { MP_ROM_QSTR(MP_QSTR_set_antialiasing), MP_ROM_PTR(&VECTOR_set_antialiasing_obj) }, { MP_ROM_QSTR(MP_QSTR_set_transform), MP_ROM_PTR(&VECTOR_set_transform_obj) }, { MP_ROM_QSTR(MP_QSTR_set_clip), MP_ROM_PTR(&VECTOR_set_clip_obj) }, diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index 6489f7ccf..fec61fc0c 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -507,6 +507,17 @@ mp_obj_t TRANSFORM_translate(mp_obj_t self_in, mp_obj_t x_in, mp_obj_t y_in) { return mp_const_none; } +mp_obj_t TRANSFORM_scale(mp_obj_t self_in, mp_obj_t x_in, mp_obj_t y_in) { + _TRANSFORM_obj_t *transform = MP_OBJ_TO_PTR2(self_in, _TRANSFORM_obj_t); + + picovector_point_type o_x = mp_picovector_get_point_type(x_in); + picovector_point_type o_y = mp_picovector_get_point_type(y_in); + + pp_mat3_scale(&transform->transform, o_x, o_y); + + return mp_const_none; +} + mp_obj_t TRANSFORM_reset(mp_obj_t self_in) { _TRANSFORM_obj_t *transform = MP_OBJ_TO_PTR2(self_in, _TRANSFORM_obj_t); transform->transform = pp_mat3_identity(); @@ -583,12 +594,34 @@ mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size) { (void)self; 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); return mp_const_none; } +mp_obj_t VECTOR_set_font_word_spacing(mp_obj_t self_in, mp_obj_t spacing) { + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); + (void)self; + + self->vector->set_font_word_spacing(mp_obj_get_int(spacing)); + return mp_const_none; +} + +mp_obj_t VECTOR_set_font_letter_spacing(mp_obj_t self_in, mp_obj_t spacing) { + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); + (void)self; + + self->vector->set_font_letter_spacing(mp_obj_get_int(spacing)); + return mp_const_none; +} + +mp_obj_t VECTOR_set_font_line_height(mp_obj_t self_in, mp_obj_t spacing) { + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); + (void)self; + + self->vector->set_font_line_height(mp_obj_get_int(spacing)); + return mp_const_none; +} + mp_obj_t VECTOR_set_clip(mp_obj_t self_in, mp_obj_t clip_in) { _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); (void)self; @@ -657,6 +690,7 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) } pp_mat3_translate(&tt, (float)x, (float)y); + //pp_mat3_mul(&tt, _pp_transform); //mp_printf(&mp_plat_print, "self->vector->text()\n"); //__printf_debug_flush(); diff --git a/micropython/modules/picovector/picovector.h b/micropython/modules/picovector/picovector.h index 2bb0e74a9..9eb46fad4 100644 --- a/micropython/modules/picovector/picovector.h +++ b/micropython/modules/picovector/picovector.h @@ -26,6 +26,7 @@ extern mp_obj_t POLYGON__del__(mp_obj_t self_in); extern mp_obj_t TRANSFORM_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); extern mp_obj_t TRANSFORM_rotate(mp_obj_t self_in, mp_obj_t angle_in, mp_obj_t origin_in); extern mp_obj_t TRANSFORM_translate(mp_obj_t self_in, mp_obj_t x_in, mp_obj_t y_in); +extern mp_obj_t TRANSFORM_scale(mp_obj_t self_in, mp_obj_t x_in, mp_obj_t y_in); extern mp_obj_t TRANSFORM_reset(mp_obj_t self_in); /* Vector */ @@ -35,6 +36,9 @@ extern mp_obj_t VECTOR_make_new(const mp_obj_type_t *type, size_t n_args, size_t extern mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size); extern mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size); +extern mp_obj_t VECTOR_set_font_word_spacing(mp_obj_t self_in, mp_obj_t size); +extern mp_obj_t VECTOR_set_font_letter_spacing(mp_obj_t self_in, mp_obj_t size); +extern mp_obj_t VECTOR_set_font_line_height(mp_obj_t self_in, mp_obj_t size); extern mp_obj_t VECTOR_set_antialiasing(mp_obj_t self_in, mp_obj_t aa); extern mp_obj_t VECTOR_set_transform(mp_obj_t self_in, mp_obj_t transform_in); extern mp_obj_t VECTOR_set_clip(mp_obj_t self_in, mp_obj_t clip_in); From 5bc852ebd0cecbe06db4e0cb40ffece3c0f5cc6a Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 9 Oct 2024 15:05:49 +0100 Subject: [PATCH 26/35] PicoGraphics: RGB565 skip layers if not enabled. --- libraries/pico_graphics/pico_graphics_pen_rgb565.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp index 432767e2b..8c2127d3a 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp @@ -106,7 +106,7 @@ namespace pimoroni { for(int x = 0; x < tile->w; x++) { uint16_t dest = *p_dest; - if(dest == 0) { + if(dest == 0 && this->layers > 1) { dest = *p_layer0; } uint8_t alpha = *p_alpha; From a292905d4d7b7620f927134ff88f56cd5af1b8bc Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 9 Oct 2024 15:06:51 +0100 Subject: [PATCH 27/35] PicoVector: Refactor text multiline support. Drop dependence on null terminated strings, and for a final linebreak. Bound all text processing using the text length. --- libraries/pico_vector/alright-fonts.h | 150 +++++++++++++----- libraries/pico_vector/pico_vector.hpp | 9 ++ micropython/modules/picovector/picovector.c | 14 +- micropython/modules/picovector/picovector.cpp | 66 ++++++-- micropython/modules/picovector/picovector.h | 2 + 5 files changed, 184 insertions(+), 57 deletions(-) diff --git a/libraries/pico_vector/alright-fonts.h b/libraries/pico_vector/alright-fonts.h index 03166bd18..a773c8104 100644 --- a/libraries/pico_vector/alright-fonts.h +++ b/libraries/pico_vector/alright-fonts.h @@ -98,14 +98,14 @@ typedef struct { 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 + unsigned int 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); +void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm); +pp_rect_t af_measure(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm); #ifdef AF_USE_PRETTY_POLY #endif @@ -240,10 +240,9 @@ void af_render_character(af_face_t *face, const char c, af_text_metrics_t *tm) { af_render_glyph(glyph, tm); } -int get_line_width(af_face_t *face, const char *text, af_text_metrics_t *tm) { +int get_line_width(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) { int line_width = 0; - char *end = strchr(text, '\n'); - if (!end) end = (char *)text + strlen(text); + char *end = (char *)text + tlen; for(char c = *text; text < end; text++, c = *text) { af_glyph_t *glyph = find_glyph(face, c); if(!glyph) { @@ -259,29 +258,44 @@ int get_line_width(af_face_t *face, const char *text, af_text_metrics_t *tm) { return line_width; } -int get_max_line_width(af_face_t *face, const char *text, af_text_metrics_t *tm) { +size_t line_length(const char *text, const char *end) { + if(text >= end) return 0; + + char *line_ending = (char *)memchr(text, '\n', end - text); + + if(line_ending == NULL || line_ending > end) { + line_ending = (char *)end; + } + + return line_ending - text; +} + +int get_max_line_width(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) { int max_width = 0; + char *line = (char *)text; + char *tend = line + tlen; - char *end = strchr(text, '\n'); - while(end) { - int width = get_line_width(face, text, tm); + size_t line_len = line_length(line, tend); + while(line_len) { + int width = get_line_width(face, line, line_len, tm); max_width = max_width < width ? width : max_width; - text = end + 1; - end = strchr(text, '\n'); + line += line_len + 1; + line_len = line_length(line, tend); } return max_width; } void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) { + char *line = (char *)text; + char *tend = line + tlen; + size_t line_len = 0; + 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; @@ -289,14 +303,17 @@ void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t caret.x = 0; caret.y = 0; - char *done = (char *)text + tlen; - char *end = strchr(text, '\n'); - if (!end) end = done; + // find maximum line length + int max_line_width = get_max_line_width(face, text, tlen, tm); + + line_len = line_length(line, tend); + + while(line_len) { + char *end = line + line_len; - while(true) { - int line_width = get_line_width(face, text, tm); + int line_width = get_line_width(face, line, line_len, tm); - for(char c = *text; text < end; text++, c = *text) { + for(char c = *line; line < end; line++, c = *line) { af_glyph_t *glyph = find_glyph(face, c); if(!glyph) { continue; @@ -306,11 +323,11 @@ void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t pp_mat3_scale(&caret_transform, scale, scale); pp_mat3_translate(&caret_transform, caret.x, caret.y); - if(tm->align == AF_H_ALIGN_CENTER) { + 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) { + if(tm->align & AF_H_ALIGN_RIGHT) { pp_mat3_translate(&caret_transform, (max_line_width - line_width), 0); } @@ -326,10 +343,8 @@ void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t } - text = end + 1; - if (*text == '\0' || text > done) break; - end = strchr(text, '\n'); - if (!end) end = (char *)text + tlen; + line += 1; // Skip over \n + line_len = line_length(line, tend); caret.x = 0; caret.y += line_height; @@ -344,26 +359,75 @@ void _af_render(af_face_t *face, const char *text, af_text_metrics_t *tm) { af_render(face, text, strlen(text), tm); } -pp_rect_t af_measure(af_face_t *face, const char *text, af_text_metrics_t *tm) { +pp_rect_t af_measure(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) { pp_rect_t result; bool first = true; - pp_mat3_t t = *tm->transform; + char *line = (char *)text; + char *tend = line + tlen; + size_t line_len = 0; + + float line_height = (tm->line_height * 128.0f) / 100.0f; + float scale = tm->size / 128.0f; + + struct { + float x, y; + } caret; + + caret.x = 0; + caret.y = 0; + + // find maximum line length + int max_line_width = get_max_line_width(face, text, tlen, tm); + + line_len = line_length(line, tend); + + while(line_len) { + char *end = line + line_len; + + int line_width = get_line_width(face, line, line_len, tm); + + for(char c = *line; line < end; line++, c = *line) { + 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_rect_t r = {glyph->x, glyph->y, glyph->x + glyph->w, glyph->y + glyph->h}; + //pp_rect_t r = af_glyph_bounds(glyph, tm); + r = pp_rect_transform(&r, &caret_transform); + + if(first) { + result = r; + first = false; + } else { + result = pp_rect_merge(&result, &r); + } + + if(c == L' ') { + caret.x += (glyph->advance * tm->word_spacing) / 100.0f; + } else { + caret.x += (glyph->advance * tm->letter_spacing) / 100.0f; + } - 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); } + + line += 1; // Skip over \n + line_len = line_length(line, tend); + + caret.x = 0; + caret.y += line_height; } return result; diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index c982dd0ea..2e88d8759 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -79,6 +79,15 @@ namespace pimoroni { text_metrics.line_height = font_line_height; } + void set_font_align(unsigned int font_align) { + text_metrics.align = font_align; + } + + pp_rect_t measure_text(std::string_view text, pp_mat3_t *t) { + text_metrics.transform = t; + return af_measure(text_metrics.face, text.data(), text.size(), &text_metrics); + } + bool set_font(std::string_view font_path, unsigned int font_size) { if(text_metrics.face) { af_free(text_metrics.face->glyphs); diff --git a/micropython/modules/picovector/picovector.c b/micropython/modules/picovector/picovector.c index bc87a0dd5..5e025c657 100644 --- a/micropython/modules/picovector/picovector.c +++ b/micropython/modules/picovector/picovector.c @@ -80,11 +80,13 @@ MP_DEFINE_CONST_OBJ_TYPE( /* PicoVector */ static MP_DEFINE_CONST_FUN_OBJ_KW(VECTOR_text_obj, 4, VECTOR_text); +static MP_DEFINE_CONST_FUN_OBJ_KW(VECTOR_measure_text_obj, 2, VECTOR_measure_text); static MP_DEFINE_CONST_FUN_OBJ_3(VECTOR_set_font_obj, VECTOR_set_font); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_size_obj, VECTOR_set_font_size); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_word_spacing_obj, VECTOR_set_font_word_spacing); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_letter_spacing_obj, VECTOR_set_font_letter_spacing); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_line_height_obj, VECTOR_set_font_line_height); +static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_align_obj, VECTOR_set_font_align); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_antialiasing_obj, VECTOR_set_antialiasing); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_transform_obj, VECTOR_set_transform); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_clip_obj, VECTOR_set_clip); @@ -92,15 +94,18 @@ static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_clip_obj, VECTOR_set_clip); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_draw_obj, VECTOR_draw); static const mp_rom_map_elem_t VECTOR_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_text), MP_ROM_PTR(&VECTOR_text_obj) }, + { MP_ROM_QSTR(MP_QSTR_measure_text), MP_ROM_PTR(&VECTOR_measure_text_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_font), MP_ROM_PTR(&VECTOR_set_font_obj) }, { MP_ROM_QSTR(MP_QSTR_set_font_size), MP_ROM_PTR(&VECTOR_set_font_size_obj) }, { MP_ROM_QSTR(MP_QSTR_set_font_word_spacing), MP_ROM_PTR(&VECTOR_set_font_word_spacing_obj) }, { MP_ROM_QSTR(MP_QSTR_set_font_letter_spacing), MP_ROM_PTR(&VECTOR_set_font_letter_spacing_obj) }, { MP_ROM_QSTR(MP_QSTR_set_font_line_height), MP_ROM_PTR(&VECTOR_set_font_line_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_font_align), MP_ROM_PTR(&VECTOR_set_font_align_obj) }, { MP_ROM_QSTR(MP_QSTR_set_antialiasing), MP_ROM_PTR(&VECTOR_set_antialiasing_obj) }, { MP_ROM_QSTR(MP_QSTR_set_transform), MP_ROM_PTR(&VECTOR_set_transform_obj) }, { MP_ROM_QSTR(MP_QSTR_set_clip), MP_ROM_PTR(&VECTOR_set_clip_obj) }, - { MP_ROM_QSTR(MP_QSTR_text), MP_ROM_PTR(&VECTOR_text_obj) }, { MP_ROM_QSTR(MP_QSTR_draw), MP_ROM_PTR(&VECTOR_draw_obj) }, }; @@ -127,6 +132,13 @@ static const mp_map_elem_t VECTOR_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_X16), MP_ROM_INT(2) }, { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_FAST), MP_ROM_INT(1) }, { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_BEST), MP_ROM_INT(2) }, + + { MP_ROM_QSTR(MP_QSTR_HALIGN_LEFT), MP_ROM_INT(0) }, + { MP_ROM_QSTR(MP_QSTR_HALIGN_CENTER), MP_ROM_INT(1) }, + { MP_ROM_QSTR(MP_QSTR_HALIGN_RIGHT), MP_ROM_INT(2) }, + { MP_ROM_QSTR(MP_QSTR_VALIGN_TOP), MP_ROM_INT(8) }, + { MP_ROM_QSTR(MP_QSTR_VALIGN_MIDDLE), MP_ROM_INT(16) }, + { MP_ROM_QSTR(MP_QSTR_VALIGN_BOTTOM), MP_ROM_INT(32) }, }; static MP_DEFINE_CONST_DICT(mp_module_VECTOR_globals, VECTOR_globals_table); diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index fec61fc0c..9b82f1430 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -572,7 +572,6 @@ mp_obj_t VECTOR_set_transform(mp_obj_t self_in, mp_obj_t transform_in) { mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size) { _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); - (void)self; int font_size = mp_obj_get_int(size); (void)font_size; @@ -591,7 +590,6 @@ mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size) { mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size) { _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); - (void)self; int font_size = mp_obj_get_int(size); self->vector->set_font_size(font_size); @@ -600,7 +598,6 @@ mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size) { mp_obj_t VECTOR_set_font_word_spacing(mp_obj_t self_in, mp_obj_t spacing) { _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); - (void)self; self->vector->set_font_word_spacing(mp_obj_get_int(spacing)); return mp_const_none; @@ -608,7 +605,6 @@ mp_obj_t VECTOR_set_font_word_spacing(mp_obj_t self_in, mp_obj_t spacing) { mp_obj_t VECTOR_set_font_letter_spacing(mp_obj_t self_in, mp_obj_t spacing) { _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); - (void)self; self->vector->set_font_letter_spacing(mp_obj_get_int(spacing)); return mp_const_none; @@ -616,15 +612,66 @@ mp_obj_t VECTOR_set_font_letter_spacing(mp_obj_t self_in, mp_obj_t spacing) { mp_obj_t VECTOR_set_font_line_height(mp_obj_t self_in, mp_obj_t spacing) { _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); - (void)self; self->vector->set_font_line_height(mp_obj_get_int(spacing)); return mp_const_none; } +mp_obj_t VECTOR_set_font_align(mp_obj_t self_in, mp_obj_t align) { + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); + + self->vector->set_font_align(mp_obj_get_int(align)); + return mp_const_none; +} + +mp_obj_t VECTOR_measure_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_text, ARG_x, ARG_y, ARG_angle }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_text, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_x, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_y, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_angle, MP_ARG_OBJ, {.u_obj = mp_const_none} } + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _VECTOR_obj_t); + + mp_obj_t text_obj = args[ARG_text].u_obj; + + if(!mp_obj_is_str_or_bytes(text_obj)) mp_raise_TypeError("text: string required"); + + GET_STR_DATA_LEN(text_obj, 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; + + 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); + + pp_rect_t bounds = self->vector->measure_text(t, &tt); + + // TODO: Should probably add the transformations available to text here? + mp_obj_t tuple[4]; + tuple[0] = mp_picovector_set_point_type((int)(bounds.x)); + tuple[1] = mp_picovector_set_point_type((int)(bounds.y)); + tuple[2] = mp_picovector_set_point_type((int)(bounds.w)); + tuple[3] = mp_picovector_set_point_type((int)(bounds.h)); + + return mp_obj_new_tuple(4, tuple); +} + mp_obj_t VECTOR_set_clip(mp_obj_t self_in, mp_obj_t clip_in) { _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); - (void)self; picovector_point_type x = self->vector->graphics->bounds.x; picovector_point_type y = self->vector->graphics->bounds.y; @@ -668,7 +715,6 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _VECTOR_obj_t); - (void)self; mp_obj_t text_obj = args[ARG_text].u_obj; @@ -680,8 +726,6 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) int x = args[ARG_x].u_int; int y = args[ARG_y].u_int; - (void)x; - (void)y; pp_mat3_t tt = pp_mat3_identity(); @@ -690,10 +734,6 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) } pp_mat3_translate(&tt, (float)x, (float)y); - //pp_mat3_mul(&tt, _pp_transform); - - //mp_printf(&mp_plat_print, "self->vector->text()\n"); - //__printf_debug_flush(); self->vector->text(t, &tt); diff --git a/micropython/modules/picovector/picovector.h b/micropython/modules/picovector/picovector.h index 9eb46fad4..cff667612 100644 --- a/micropython/modules/picovector/picovector.h +++ b/micropython/modules/picovector/picovector.h @@ -34,11 +34,13 @@ extern mp_obj_t TRANSFORM_reset(mp_obj_t self_in); extern mp_obj_t VECTOR_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); extern mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t VECTOR_measure_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size); extern mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size); extern mp_obj_t VECTOR_set_font_word_spacing(mp_obj_t self_in, mp_obj_t size); extern mp_obj_t VECTOR_set_font_letter_spacing(mp_obj_t self_in, mp_obj_t size); extern mp_obj_t VECTOR_set_font_line_height(mp_obj_t self_in, mp_obj_t size); +extern mp_obj_t VECTOR_set_font_align(mp_obj_t self_in, mp_obj_t align); extern mp_obj_t VECTOR_set_antialiasing(mp_obj_t self_in, mp_obj_t aa); extern mp_obj_t VECTOR_set_transform(mp_obj_t self_in, mp_obj_t transform_in); extern mp_obj_t VECTOR_set_clip(mp_obj_t self_in, mp_obj_t clip_in); From 49b496d29a9e2b67642d29c3504541880159bb1e Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 10 Oct 2024 11:21:06 +0100 Subject: [PATCH 28/35] PicoVector: Remove (ifdef guard) debug functions. --- micropython/modules/picovector/picovector.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index 9b82f1430..df0919cd7 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -35,6 +35,7 @@ typedef struct _POLY_obj_t { pp_poly_t *poly; } _POLY_obj_t; +#if DEBUG void __printf_debug_flush() { for(auto i = 0u; i < 10; i++) { sleep_ms(1); @@ -42,9 +43,6 @@ void __printf_debug_flush() { } } -#define mp_picovector_get_point_type mp_obj_get_float -#define mp_picovector_set_point_type mp_obj_new_float - int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args); void af_debug(const char *fmt, ...) { @@ -55,6 +53,11 @@ void af_debug(const char *fmt, ...) { __printf_debug_flush(); (void)ret; } +#endif + +#define mp_picovector_get_point_type mp_obj_get_float +#define mp_picovector_set_point_type mp_obj_new_float + void *af_malloc(size_t size) { //mp_printf(&mp_plat_print, "af_malloc %lu\n", size); From 03a8d5152657342de64d9b4180c8f1dbb1d133a5 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 14 Aug 2024 14:20:18 +0100 Subject: [PATCH 29/35] PicoGraphics: Add Presto. --- micropython/modules/picographics/picographics.c | 1 + micropython/modules/picographics/picographics.cpp | 10 +++++++++- micropython/modules/picographics/picographics.h | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/micropython/modules/picographics/picographics.c b/micropython/modules/picographics/picographics.c index 1a504c1ea..b90071ff9 100644 --- a/micropython/modules/picographics/picographics.c +++ b/micropython/modules/picographics/picographics.c @@ -165,6 +165,7 @@ static const mp_map_elem_t picographics_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_DISPLAY_SCROLL_PACK), MP_ROM_INT(DISPLAY_SCROLL_PACK) }, { MP_ROM_QSTR(MP_QSTR_DISPLAY_PICO_W_EXPLORER), MP_ROM_INT(DISPLAY_PICO_W_EXPLORER) }, { MP_ROM_QSTR(MP_QSTR_DISPLAY_EXPLORER), MP_ROM_INT(DISPLAY_EXPLORER) }, + { MP_ROM_QSTR(MP_QSTR_DISPLAY_PRESTO), MP_ROM_INT(DISPLAY_PRESTO) }, { MP_ROM_QSTR(MP_QSTR_PEN_1BIT), MP_ROM_INT(PEN_1BIT) }, { MP_ROM_QSTR(MP_QSTR_PEN_P4), MP_ROM_INT(PEN_P4) }, diff --git a/micropython/modules/picographics/picographics.cpp b/micropython/modules/picographics/picographics.cpp index 218c95788..0253e075e 100644 --- a/micropython/modules/picographics/picographics.cpp +++ b/micropython/modules/picographics/picographics.cpp @@ -248,6 +248,13 @@ bool get_display_settings(PicoGraphicsDisplay display, int &width, int &height, if(rotate == -1) rotate = (int)Rotation::ROTATE_0; if(pen_type == -1) pen_type = PEN_RGB888; break; + case DISPLAY_PRESTO: + width = 240; + height = 240; + bus_type = BUS_PIO; + rotate = (int)Rotation::ROTATE_0; + pen_type = PEN_RGB565; + break; default: return false; } @@ -388,7 +395,8 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size || display == DISPLAY_COSMIC_UNICORN || display == DISPLAY_STELLAR_UNICORN || display == DISPLAY_UNICORN_PACK - || display == DISPLAY_SCROLL_PACK) { + || display == DISPLAY_SCROLL_PACK + || display == DISPLAY_PRESTO) { // Create a dummy display driver self->display = m_new_class(DisplayDriver, width, height, (Rotation)rotate); diff --git a/micropython/modules/picographics/picographics.h b/micropython/modules/picographics/picographics.h index 6457599ab..87225716d 100644 --- a/micropython/modules/picographics/picographics.h +++ b/micropython/modules/picographics/picographics.h @@ -31,7 +31,8 @@ enum PicoGraphicsDisplay { DISPLAY_UNICORN_PACK, DISPLAY_SCROLL_PACK, DISPLAY_PICO_W_EXPLORER, - DISPLAY_EXPLORER + DISPLAY_EXPLORER, + DISPLAY_PRESTO }; enum PicoGraphicsPenType { From f2d84fa32137b727d62eb2f98e627d366b286c6c Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 24 Oct 2024 14:08:18 +0100 Subject: [PATCH 30/35] Plasma: Add support for GPIOs >=32. For both APA102 and WS2812 the pins used on the same PIO must be in the same range. The GPIO base offset applies to the whole PIO and not individual state machines. This means that for APA102 both data and clock must be in the same pin range, ie: either 16-48 inclusive or 0-31 inclusive. --- drivers/plasma/apa102.cpp | 20 ++++++++++++++++++-- drivers/plasma/ws2812.cpp | 3 +++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/plasma/apa102.cpp b/drivers/plasma/apa102.cpp index 443a35ec3..87bdf3289 100644 --- a/drivers/plasma/apa102.cpp +++ b/drivers/plasma/apa102.cpp @@ -4,10 +4,26 @@ namespace plasma { APA102::APA102(uint num_leds, PIO pio, uint sm, uint pin_dat, uint pin_clk, uint freq, RGB* buffer) : buffer(buffer), num_leds(num_leds), pio(pio), sm(sm) { + // NOTE: This sets the gpio_base for *the entire PIO* not just this state machine + uint range_max = std::max(pin_dat, pin_clk); + uint range_min = std::min(pin_dat, pin_clk); + + // Both pins in 16-48 range + if(range_max >= 32 && range_min >= 16) { + pio_set_gpio_base(pio, 16); + // Both pins in 0-31 range + } else if(range_max <= 31) { + pio_set_gpio_base(pio, 0); + // Pins in different ranges: invalid combo! + } else { + // TODO: Need some means to notify the caller + pio_set_gpio_base(pio, 0); + } + pio_program_offset = pio_add_program(pio, &apa102_program); - pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_clk) | (1u << pin_dat)); - pio_sm_set_pindirs_with_mask(pio, sm, ~0u, (1u << pin_clk) | (1u << pin_dat)); + pio_sm_set_pins_with_mask(pio, sm, 0, (1u << (pin_clk - pio_get_gpio_base(pio))) | (1u << (pin_dat - pio_get_gpio_base(pio)))); + pio_sm_set_pindirs_with_mask(pio, sm, ~0u, (1u << (pin_clk - pio_get_gpio_base(pio))) | (1u << (pin_dat - pio_get_gpio_base(pio)))); pio_gpio_init(pio, pin_clk); pio_gpio_init(pio, pin_dat); diff --git a/drivers/plasma/ws2812.cpp b/drivers/plasma/ws2812.cpp index db6ae4b95..4da6d9329 100644 --- a/drivers/plasma/ws2812.cpp +++ b/drivers/plasma/ws2812.cpp @@ -4,6 +4,9 @@ namespace plasma { WS2812::WS2812(uint num_leds, PIO pio, uint sm, uint pin, uint freq, bool rgbw, COLOR_ORDER color_order, RGB* buffer) : buffer(buffer), num_leds(num_leds), color_order(color_order), pio(pio), sm(sm) { + // NOTE: This sets the gpio_base for *the entire PIO* not just this state machine + pio_set_gpio_base(pio, pin >= 32 ? 16 : 0); + pio_program_offset = pio_add_program(pio, &ws2812_program); pio_gpio_init(pio, pin); From 96beb08d046d0f3c3c7a10cf8f20d94c017a6cc2 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 31 Oct 2024 13:18:07 +0000 Subject: [PATCH 31/35] PicoGraphics: Non-blocking Inky update for #936. --- .../modules/picographics/picographics.c | 6 ++++ .../modules/picographics/picographics.cpp | 36 +++++++++++++------ .../modules/picographics/picographics.h | 3 ++ 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/micropython/modules/picographics/picographics.c b/micropython/modules/picographics/picographics.c index b90071ff9..6dfd08683 100644 --- a/micropython/modules/picographics/picographics.c +++ b/micropython/modules/picographics/picographics.c @@ -12,6 +12,8 @@ MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics_update_obj, ModPicoGraphics_update); MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_partial_update_obj, 5, 5, ModPicoGraphics_partial_update); MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_backlight_obj, ModPicoGraphics_set_backlight); MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_update_speed_obj, ModPicoGraphics_set_update_speed); +MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_blocking_obj, ModPicoGraphics_set_blocking); +MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics_is_busy_obj, ModPicoGraphics_is_busy); // Palette management MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_update_pen_obj, 5, 5, ModPicoGraphics_update_pen); @@ -69,6 +71,10 @@ static const mp_rom_map_elem_t ModPicoGraphics_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&ModPicoGraphics_update_obj) }, { MP_ROM_QSTR(MP_QSTR_partial_update), MP_ROM_PTR(&ModPicoGraphics_partial_update_obj) }, { MP_ROM_QSTR(MP_QSTR_set_update_speed), MP_ROM_PTR(&ModPicoGraphics_set_update_speed_obj) }, + + { MP_ROM_QSTR(MP_QSTR_set_blocking), MP_ROM_PTR(&ModPicoGraphics_set_blocking_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_busy), MP_ROM_PTR(&ModPicoGraphics_is_busy_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_clip), MP_ROM_PTR(&ModPicoGraphics_set_clip_obj) }, { MP_ROM_QSTR(MP_QSTR_get_clip), MP_ROM_PTR(&ModPicoGraphics_get_clip_obj) }, { MP_ROM_QSTR(MP_QSTR_remove_clip), MP_ROM_PTR(&ModPicoGraphics_remove_clip_obj) }, diff --git a/micropython/modules/picographics/picographics.cpp b/micropython/modules/picographics/picographics.cpp index 0253e075e..841e06cca 100644 --- a/micropython/modules/picographics/picographics.cpp +++ b/micropython/modules/picographics/picographics.cpp @@ -39,6 +39,7 @@ typedef struct _ModPicoGraphics_obj_t { void *buffer; void *fontdata; _PimoroniI2C_obj_t *i2c; + bool blocking = true; //mp_obj_t scanline_callback; // Not really feasible in MicroPython } ModPicoGraphics_obj_t; @@ -643,6 +644,17 @@ mp_obj_t ModPicoGraphics_set_scanline_callback(mp_obj_t self_in, mp_obj_t cb_in) } */ +mp_obj_t ModPicoGraphics_set_blocking(mp_obj_t self_in, mp_obj_t blocking_in) { + ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t); + self->blocking = blocking_in == mp_const_true; + return mp_const_none; +} + +mp_obj_t ModPicoGraphics_is_busy(mp_obj_t self_in) { + ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t); + return self->display->is_busy() ? mp_const_true : mp_const_false; +} + mp_obj_t ModPicoGraphics_update(mp_obj_t self_in) { ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t); /* @@ -666,13 +678,15 @@ mp_obj_t ModPicoGraphics_update(mp_obj_t self_in) { self->display->update(self->graphics); - while(self->display->is_busy()) { - #ifdef mp_event_handle_nowait - mp_event_handle_nowait(); - #endif - } + if(self->blocking) { + while(self->display->is_busy()) { + #ifdef mp_event_handle_nowait + mp_event_handle_nowait(); + #endif + } - self->display->power_off(); + self->display->power_off(); + } return mp_const_none; } @@ -695,10 +709,12 @@ mp_obj_t ModPicoGraphics_partial_update(size_t n_args, const mp_obj_t *args) { mp_obj_get_int(args[ARG_h]) }); - while(self->display->is_busy()) { - #ifdef mp_event_handle_nowait - mp_event_handle_nowait(); - #endif + if(self->blocking) { + while(self->display->is_busy()) { + #ifdef mp_event_handle_nowait + mp_event_handle_nowait(); + #endif + } } return mp_const_none; diff --git a/micropython/modules/picographics/picographics.h b/micropython/modules/picographics/picographics.h index 87225716d..f1313ff1b 100644 --- a/micropython/modules/picographics/picographics.h +++ b/micropython/modules/picographics/picographics.h @@ -72,6 +72,9 @@ extern mp_obj_t ModPicoGraphics_partial_update(size_t n_args, const mp_obj_t *ar extern mp_obj_t ModPicoGraphics_set_backlight(mp_obj_t self_in, mp_obj_t brightness); extern mp_obj_t ModPicoGraphics_set_update_speed(mp_obj_t self_in, mp_obj_t update_speed); +extern mp_obj_t ModPicoGraphics_set_blocking(mp_obj_t self_in, mp_obj_t blocking_in); +extern mp_obj_t ModPicoGraphics_is_busy(mp_obj_t self_in); + // Palette management extern mp_obj_t ModPicoGraphics_update_pen(size_t n_args, const mp_obj_t *args); extern mp_obj_t ModPicoGraphics_reset_pen(mp_obj_t self_in, mp_obj_t pen); From 225ecfcff9155a1c5211506fc44d98da79c57364 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 14 Nov 2024 21:31:52 +0000 Subject: [PATCH 32/35] PicoVector: Prefix some pretty-poly variables. --- libraries/pico_vector/pretty-poly.h | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/libraries/pico_vector/pretty-poly.h b/libraries/pico_vector/pretty-poly.h index 5fc1f80fb..c92abec45 100644 --- a/libraries/pico_vector/pretty-poly.h +++ b/libraries/pico_vector/pretty-poly.h @@ -382,12 +382,12 @@ pp_rect_t pp_poly_bounds(pp_poly_t *p) { // buffer that each tile is rendered into before callback // allocate one extra byte to allow a small optimization in the row renderer -uint8_t tile_buffer[PP_TILE_BUFFER_SIZE * PP_TILE_BUFFER_SIZE]; +uint8_t pp_tile_buffer[PP_TILE_BUFFER_SIZE * PP_TILE_BUFFER_SIZE]; // 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_TILE_BUFFER_SIZE * 4][PP_MAX_NODES_PER_SCANLINE * 2]; -uint32_t node_counts[PP_TILE_BUFFER_SIZE * 4]; +int32_t pp_nodes[PP_TILE_BUFFER_SIZE * 4][PP_MAX_NODES_PER_SCANLINE * 2]; +uint32_t pp_node_counts[PP_TILE_BUFFER_SIZE * 4]; uint8_t _pp_alpha_map_none[2] = {0, 255}; uint8_t _pp_alpha_map_x4[5] = {0, 63, 127, 190, 255}; @@ -474,7 +474,7 @@ void add_line_segment_to_nodes(const pp_point_t start, const pp_point_t end, pp_ // 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; +// pp_nodes[y][pp_node_counts[y]++] = nx; // // step to next scanline and accumulate error // y++; @@ -490,7 +490,7 @@ void add_line_segment_to_nodes(const pp_point_t start, const pp_point_t end, pp_ int nx = _pp_max(_pp_min(x, (tb->w << _pp_antialias)), 0); //debug(" + adding node at %d, %d\n", x, y); // add node to node list - nodes[y][node_counts[y]++] = nx; + pp_nodes[y][pp_node_counts[y]++] = nx; // step to next scanline and accumulate error y++; @@ -531,17 +531,17 @@ pp_rect_t render_nodes(pp_rect_t *tb) { for(int y = 0; y < ((int)PP_TILE_BUFFER_SIZE << _pp_antialias); y++) { - // debug(" : row %d node count %d\n", y, node_counts[y]); + // debug(" : row %d node count %d\n", y, pp_node_counts[y]); - if(node_counts[y] == 0) continue; // no nodes on this raster line + if(pp_node_counts[y] == 0) continue; // no nodes on this raster line - qsort(&nodes[y][0], node_counts[y], sizeof(int), compare_nodes); + qsort(&pp_nodes[y][0], pp_node_counts[y], sizeof(int), compare_nodes); - unsigned char* row_data = &tile_buffer[(y >> _pp_antialias) * PP_TILE_BUFFER_SIZE]; + unsigned char* row_data = &pp_tile_buffer[(y >> _pp_antialias) * PP_TILE_BUFFER_SIZE]; - for(uint32_t i = 0; i < node_counts[y]; i += 2) { - int sx = nodes[y][i + 0]; - int ex = nodes[y][i + 1]; + for(uint32_t i = 0; i < pp_node_counts[y]; i += 2) { + int sx = pp_nodes[y][i + 0]; + int ex = pp_nodes[y][i + 1]; if(sx == ex) { // empty span, nothing to do continue; @@ -584,7 +584,7 @@ pp_rect_t render_nodes(pp_rect_t *tb) { if(_pp_antialias == 2) p_alpha_map = _pp_alpha_map_x16; #if PP_SCALE_TO_ALPHA == 1 for(int y = rb.y; y < rb.y + rb.h; y++) { - unsigned char* row_data = &tile_buffer[y * PP_TILE_BUFFER_SIZE + rb.x]; + unsigned char* row_data = &pp_tile_buffer[y * PP_TILE_BUFFER_SIZE + rb.x]; for(int x = rb.x; x < rb.x + rb.w; x++) { *row_data = p_alpha_map[*row_data]; row_data++; @@ -636,8 +636,8 @@ void pp_render(pp_poly_t *polygon) { 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, PP_TILE_BUFFER_SIZE * PP_TILE_BUFFER_SIZE); + memset(pp_node_counts, 0, sizeof(pp_node_counts)); + memset(pp_tile_buffer, 0, PP_TILE_BUFFER_SIZE * PP_TILE_BUFFER_SIZE); // build the nodes for each pp_path_t pp_path_t *path = polygon->paths; @@ -659,7 +659,7 @@ void pp_render(pp_poly_t *polygon) { pp_tile_t tile = { .x = tb.x, .y = tb.y, .w = tb.w, .h = tb.h, .stride = PP_TILE_BUFFER_SIZE, - .data = tile_buffer + rb.x + (PP_TILE_BUFFER_SIZE * rb.y) + .data = pp_tile_buffer + rb.x + (PP_TILE_BUFFER_SIZE * rb.y) }; _pp_tile_callback(&tile); From 0425d77c36bb2334c29f6b3f56575c2c8a7453c5 Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Sun, 17 Nov 2024 13:11:56 +0000 Subject: [PATCH 33/35] PicoVector: Add optional text max width and max height. --- libraries/pico_vector/alright-fonts.h | 58 ++++++++----- libraries/pico_vector/pico_vector.cpp | 84 +------------------ libraries/pico_vector/pico_vector.hpp | 4 +- micropython/modules/picovector/picovector.cpp | 10 ++- 4 files changed, 49 insertions(+), 107 deletions(-) diff --git a/libraries/pico_vector/alright-fonts.h b/libraries/pico_vector/alright-fonts.h index a773c8104..20426a3a9 100644 --- a/libraries/pico_vector/alright-fonts.h +++ b/libraries/pico_vector/alright-fonts.h @@ -31,6 +31,7 @@ #include #include #include +#include #ifdef AF_MALLOC #ifndef PP_MALLOC @@ -104,8 +105,8 @@ typedef struct { 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, size_t tlen, af_text_metrics_t *tm); -pp_rect_t af_measure(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm); +void af_render(af_face_t *face, const char *text, size_t tlen, float max_line_width, float max_height, af_text_metrics_t *tm); +pp_rect_t af_measure(af_face_t *face, const char *text, size_t tlen, float max_line_width, af_text_metrics_t *tm); #ifdef AF_USE_PRETTY_POLY #endif @@ -240,20 +241,30 @@ void af_render_character(af_face_t *face, const char c, af_text_metrics_t *tm) { af_render_glyph(glyph, tm); } -int get_line_width(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) { - int line_width = 0; - char *end = (char *)text + tlen; +float get_line_width(af_face_t *face, const char *text, size_t *tlen, float max_line_width, af_text_metrics_t *tm) { + float line_width = 0; + const char *start = text; + const char *end = text + *tlen; + const char *last_space = nullptr; for(char c = *text; text < end; text++, c = *text) { af_glyph_t *glyph = find_glyph(face, c); if(!glyph) { continue; } + float char_width; if(c == L' ') { - line_width += (glyph->advance * tm->word_spacing) / 100.0f; + char_width = (glyph->advance * tm->word_spacing) / 100.0f; + last_space = text; } else { - line_width += (glyph->advance * tm->letter_spacing) / 100.0f; + char_width = (glyph->advance * tm->letter_spacing) / 100.0f; } + + if (max_line_width > 0 && line_width + char_width > max_line_width && last_space) { + *tlen = last_space - start; + break; + } + line_width += char_width; } return line_width; } @@ -270,14 +281,14 @@ size_t line_length(const char *text, const char *end) { return line_ending - text; } -int get_max_line_width(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) { - int max_width = 0; +float get_max_line_width(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) { + float max_width = 0; char *line = (char *)text; char *tend = line + tlen; size_t line_len = line_length(line, tend); while(line_len) { - int width = get_line_width(face, line, line_len, tm); + float width = get_line_width(face, line, &line_len, 0, tm); max_width = max_width < width ? width : max_width; line += line_len + 1; line_len = line_length(line, tend); @@ -286,7 +297,7 @@ int get_max_line_width(af_face_t *face, const char *text, size_t tlen, af_text_m return max_width; } -void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) { +void af_render(af_face_t *face, const char *text, size_t tlen, float max_line_width, float max_height, af_text_metrics_t *tm) { char *line = (char *)text; char *tend = line + tlen; size_t line_len = 0; @@ -304,15 +315,23 @@ void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t caret.y = 0; // find maximum line length - int max_line_width = get_max_line_width(face, text, tlen, tm); + if (max_line_width == 0.f) { + max_line_width = get_max_line_width(face, text, tlen, tm); + } else { + max_line_width /= scale; + } + if (max_height == 0.f) { + max_height = FLT_MAX; + } else { + max_height /= scale; + } line_len = line_length(line, tend); - while(line_len) { + while(line_len && caret.y + line_height <= max_height) { + int line_width = get_line_width(face, line, &line_len, max_line_width, tm); char *end = line + line_len; - int line_width = get_line_width(face, line, line_len, tm); - for(char c = *line; line < end; line++, c = *line) { af_glyph_t *glyph = find_glyph(face, c); if(!glyph) { @@ -356,10 +375,10 @@ void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t } void _af_render(af_face_t *face, const char *text, af_text_metrics_t *tm) { - af_render(face, text, strlen(text), tm); + af_render(face, text, strlen(text), 0, 0, tm); } -pp_rect_t af_measure(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) { +pp_rect_t af_measure(af_face_t *face, const char *text, size_t tlen, float max_line_width, af_text_metrics_t *tm) { pp_rect_t result; bool first = true; char *line = (char *)text; @@ -377,15 +396,14 @@ pp_rect_t af_measure(af_face_t *face, const char *text, size_t tlen, af_text_met caret.y = 0; // find maximum line length - int max_line_width = get_max_line_width(face, text, tlen, tm); + if (max_line_width == 0.f) max_line_width = get_max_line_width(face, text, tlen, tm); line_len = line_length(line, tend); while(line_len) { + int line_width = get_line_width(face, line, &line_len, max_line_width, tm); char *end = line + line_len; - int line_width = get_line_width(face, line, line_len, tm); - for(char c = *line; line < end; line++, c = *line) { af_glyph_t *glyph = find_glyph(face, c); if(!glyph) { diff --git a/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp index 41d158826..43079a493 100644 --- a/libraries/pico_vector/pico_vector.cpp +++ b/libraries/pico_vector/pico_vector.cpp @@ -60,93 +60,13 @@ namespace pimoroni { } } - pp_point_t PicoVector::text(std::string_view text, pp_mat3_t *t) { + pp_point_t PicoVector::text(std::string_view text, int max_width, int max_height, pp_mat3_t *t) { pp_point_t caret = {0, 0}; text_metrics.transform = t; - af_render(text_metrics.face, text.data(), text.size(), &text_metrics); + af_render(text_metrics.face, text.data(), text.size(), max_width, max_height, &text_metrics); return caret; -/* - // Align text from the bottom left - caret.y += (PP_COORD_TYPE)text_metrics.line_height; - - caret = pp_point_transform(&caret, t); - caret.x += offset.x; - caret.y += offset.y; - - pp_point_t space; - pp_point_t carriage_return = {0, -(PP_COORD_TYPE)text_metrics.line_height}; - - char spc = ' '; - - space.x = af_measure(text_metrics.face, &spc, &text_metrics).w; - if (space.x == 0) { - space.x = text_metrics.word_spacing; - } - - space = pp_point_transform(&space, t); - carriage_return = pp_point_transform(&carriage_return, t); - - pp_point_t initial_carriage_return = carriage_return; - - size_t i = 0; - - while(i < text.length()) { - size_t next_space = text.find(' ', i + 1); - - if(next_space == std::string::npos) { - next_space = text.length(); - } - - size_t next_linebreak = text.find('\n', i + 1); - - if(next_linebreak == std::string::npos) { - next_linebreak = text.length(); - } - - size_t next_break = std::min(next_space, next_linebreak); - - uint16_t word_width = 0; - for(size_t j = i; j < next_break; j++) { - word_width += af_measure(text_metrics.face, &text[j], &text_metrics).w; - word_width += text_metrics.letter_spacing; - } - - if(caret.x != 0 && caret.x + word_width > graphics->clip.w) { - caret = pp_point_sub(&caret, &carriage_return); - carriage_return = initial_carriage_return; - } - - for(size_t j = i; j < std::min(next_break + 1, text.length()); j++) { - if (text[j] == '\n') { // Linebreak - caret = pp_point_sub(&caret, &carriage_return); - carriage_return = initial_carriage_return; - } else if (text[j] == ' ') { // Space - caret = pp_point_add(&caret, &space); - carriage_return = pp_point_add(&carriage_return, &space); - } else { - // 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)af_measure(text_metrics.face, &text[j], &text_metrics).w + text_metrics.letter_spacing, - (PP_COORD_TYPE)0 - }; - advance = pp_point_transform(&advance, t); - caret = pp_point_add(&caret, &advance); - carriage_return = pp_point_add(&carriage_return, &advance); - } - - i = next_break + 1; - } - - return {caret.x, caret.y}; -*/ } } \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index 2e88d8759..5808f29c8 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -85,7 +85,7 @@ namespace pimoroni { pp_rect_t measure_text(std::string_view text, pp_mat3_t *t) { text_metrics.transform = t; - return af_measure(text_metrics.face, text.data(), text.size(), &text_metrics); + return af_measure(text_metrics.face, text.data(), text.size(), 0, &text_metrics); } bool set_font(std::string_view font_path, unsigned int font_size) { @@ -116,7 +116,7 @@ namespace pimoroni { return result; } - pp_point_t text(std::string_view text, pp_mat3_t *t=nullptr); + pp_point_t text(std::string_view text, int max_width, int max_height, pp_mat3_t *t=nullptr); void transform(pp_path_t *path, pp_mat3_t *t); void transform(pp_poly_t *poly, pp_mat3_t *t); diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index df0919cd7..500f8621c 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -705,13 +705,15 @@ mp_obj_t VECTOR_set_antialiasing(mp_obj_t self_in, mp_obj_t aa) { } mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_self, ARG_text, ARG_x, ARG_y, ARG_angle }; + enum { ARG_self, ARG_text, ARG_x, ARG_y, ARG_angle, ARG_max_width, ARG_max_height }; static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_text, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_INT }, { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_INT }, - { MP_QSTR_angle, MP_ARG_OBJ, {.u_obj = mp_const_none} } + { MP_QSTR_angle, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_max_width, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_max_height, MP_ARG_INT, {.u_int = 0} } }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -729,6 +731,8 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) int x = args[ARG_x].u_int; int y = args[ARG_y].u_int; + int max_width = args[ARG_max_width].u_int; + int max_height = args[ARG_max_height].u_int; pp_mat3_t tt = pp_mat3_identity(); @@ -738,7 +742,7 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) pp_mat3_translate(&tt, (float)x, (float)y); - self->vector->text(t, &tt); + self->vector->text(t, max_width, max_height, &tt); return mp_const_none; } From a4adf0d2182dff23a3d83baf470ced16f4cc993e Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Sun, 17 Nov 2024 13:52:03 +0000 Subject: [PATCH 34/35] PicoVector: Apply overall transform to text rendering. --- libraries/pico_vector/alright-fonts.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/pico_vector/alright-fonts.h b/libraries/pico_vector/alright-fonts.h index 20426a3a9..c88826f05 100644 --- a/libraries/pico_vector/alright-fonts.h +++ b/libraries/pico_vector/alright-fonts.h @@ -350,7 +350,9 @@ void af_render(af_face_t *face, const char *text, size_t tlen, float max_line_wi pp_mat3_translate(&caret_transform, (max_line_width - line_width), 0); } - pp_transform(&caret_transform); + pp_mat3_t final_transform = *old; + pp_mat3_mul(&final_transform, &caret_transform); + pp_transform(&final_transform); af_render_glyph(glyph, tm); From 7a0fb98f7aa7b619f840e5bbc8a57c366b73d405 Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Sun, 17 Nov 2024 16:00:23 +0000 Subject: [PATCH 35/35] PicoVector: Fix C++ example compilation. --- examples/pico_display_2/pico_display_2_vector.cpp | 2 +- examples/pico_w_explorer/pico_w_explorer_vector.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/pico_display_2/pico_display_2_vector.cpp b/examples/pico_display_2/pico_display_2_vector.cpp index 47c2a468e..d267c8284 100644 --- a/examples/pico_display_2/pico_display_2_vector.cpp +++ b/examples/pico_display_2/pico_display_2_vector.cpp @@ -51,7 +51,7 @@ int main() { pp_mat3_translate(&pos, 50, 50); pp_mat3_rotate(&pos, a); vector.draw(poly); - vector.text("Hello World", &pos); + vector.text("Hello World", 0, 0, &pos); // update screen st7789.update(&graphics); diff --git a/examples/pico_w_explorer/pico_w_explorer_vector.cpp b/examples/pico_w_explorer/pico_w_explorer_vector.cpp index 82cc29d8e..6fc3b53c0 100644 --- a/examples/pico_w_explorer/pico_w_explorer_vector.cpp +++ b/examples/pico_w_explorer/pico_w_explorer_vector.cpp @@ -41,8 +41,8 @@ int main() { vector.draw(poly); - //pp_mat3_t t = pp_mat3_identity(); - //vector.text("Hello World", {0, 0}, &t); + pp_mat3_t t = pp_mat3_identity(); + vector.text("Hello World", 0, 0, &t); // update screen st7789.update(&graphics);