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); diff --git a/drivers/st7735/st7735.cpp b/drivers/st7735/st7735.cpp index d2cabff7c..b945537a8 100644 --- a/drivers/st7735/st7735.cpp +++ b/drivers/st7735/st7735.cpp @@ -165,7 +165,7 @@ namespace pimoroni { // Native 16-bit framebuffer update void ST7735::update(PicoGraphics *graphics) { - if(graphics->pen_type == PicoGraphics::PEN_RGB565) { + 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); diff --git a/drivers/st7789/st7789.cpp b/drivers/st7789/st7789.cpp index ed34447c5..9f033adc5 100644 --- a/drivers/st7789/st7789.cpp +++ b/drivers/st7789/st7789.cpp @@ -282,7 +282,7 @@ namespace pimoroni { void ST7789::update(PicoGraphics *graphics) { uint8_t cmd = reg::RAMWR; - if(graphics->pen_type == PicoGraphics::PEN_RGB565) { // Display buffer is screen native + 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 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_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..d267c8284 --- /dev/null +++ b/examples/pico_display_2/pico_display_2_vector.cpp @@ -0,0 +1,67 @@ +#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_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.text("Hello World", 0, 0, &pos); + + // update screen + st7789.update(&graphics); + a += 1; + if (a > 359) { + a = 0; + } + + pp_poly_free(poly); + } + + 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 000000000..5a935f3fa Binary files /dev/null and b/examples/pico_display_2/vector/DynaPuff-Medium.af differ 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..6fc3b53c0 --- /dev/null +++ b/examples/pico_w_explorer/pico_w_explorer_vector.cpp @@ -0,0 +1,56 @@ +#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); + + float angle = 0.0f; + + 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_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.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); + + // update screen + st7789.update(&graphics); + + angle += 1.0f; + + pp_poly_free(poly); + } + + 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_graphics/pico_graphics.cpp b/libraries/pico_graphics/pico_graphics.cpp index 4e32e4ec3..c457ca99d 100644 --- a/libraries/pico_graphics/pico_graphics.cpp +++ b/libraries/pico_graphics/pico_graphics.cpp @@ -19,6 +19,14 @@ namespace pimoroni { RGB* PicoGraphics::get_palette() {return nullptr;} bool PicoGraphics::supports_alpha_blend() {return false;} + 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; + }; + 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..9953f7549 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;} @@ -142,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 { @@ -226,6 +233,10 @@ namespace pimoroni { Rect clip; uint thickness = 1; + uint layers = 1; + uint layer = 0; + uint layer_offset = 0; + typedef std::function conversion_callback_func; typedef std::function next_pixel_func; typedef std::function next_pixel_func_rgb888; @@ -270,6 +281,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 +295,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(); @@ -293,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); @@ -330,7 +350,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 +366,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 +407,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 +440,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; @@ -440,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 { @@ -453,7 +475,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; @@ -473,12 +495,14 @@ 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 { 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; @@ -497,13 +521,15 @@ 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 { 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 +542,20 @@ 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; + 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 { 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; @@ -532,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); }; @@ -600,7 +635,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..dcb5fe19d 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)]); @@ -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,62 @@ 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]; + }); + } + } + } + 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 f7d0be73d..10f320981 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)]); @@ -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,90 @@ 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; + } + + src++; + + 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; + + // 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(); + } - // Treat our void* frame_buffer as uint8_t - uint8_t *src = (uint8_t *)frame_buffer; + frame_convert_rgb565(callback, [&]() { + return cache[*src++]; + }); + } else if (type == PEN_RGB888) { + frame_convert_rgb888(callback, [&]() { + return palette[*src++].to_rgb888(); + }); + } + } + } - 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; + 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; - frame_convert_rgb888(callback, [&]() { - return palette[*src++].to_rgb888(); - }); + 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 1bce808fc..b3136d6d5 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 += 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 = &buf[p.y * bounds.w + p.x]; + buf += this->layer_offset; + 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 += this->layer_offset; RGB332 blended = RGB(buf[p.y * bounds.w + p.x]).blend(RGB(color), a).to_rgb332(); @@ -96,9 +99,29 @@ 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) { + // The size of a single layer + uint offset = this->bounds.w * this->bounds.h; + + frame_convert_rgb565(callback, [&]() { + 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, [&]() { + return rgb332_to_rgb565_lut[*src++]; + }); + } } } void PicoGraphics_PenRGB332::sprite(void* data, const Point &sprite, const Point &dest, const int scale, const int transparent) { @@ -122,4 +145,50 @@ 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 *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 = *p_dest; + if(dest == 0) { + dest = *p_layer0; + } + + // TODO: Try to alpha blend RGB332... somewhat? + if(alpha == 255) { + *p_dest = 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 + *p_dest = (r << 5) | (g << 2) | (b); + } + + p_dest++; + palpha++; + } + } + + return true; + } } diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp index 30ef5bed6..8c2127d3a 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->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->layer_offset; buf = &buf[p.y * bounds.w + p.x]; while(l--) { @@ -35,26 +39,105 @@ 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 + // We can't use buffer_size because our pointer is uint16_t + uint16_t *src_layer2 = src + this->bounds.w * this->bounds.h; + + 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::set_pixel_alpha(const Point &p, const uint8_t a) { + 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(); + + 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; + 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); + } + } + } + + bool PicoGraphics_PenRGB565::render_tile(const Tile *tile) { + for(int y = 0; y < tile->h; y++) { + 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 = *p_dest; + if(dest == 0 && this->layers > 1) { + dest = *p_layer0; + } + uint8_t alpha = *p_alpha; + + if(alpha == 255) { + *p_dest = color; + }else if(alpha == 0) { + }else{ + // blend tha pixel + 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 = (__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 + *p_dest = __builtin_bswap16((r << 11) | (g << 5) | (b)); + } + + p_layer0++; + p_dest++; + p_alpha++; + } + } + + return true; + } } diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp index 6cc9d2adf..642557a46 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)]); @@ -23,15 +23,37 @@ 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--) { *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/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/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 new file mode 100644 index 000000000..3070f7ae9 --- /dev/null +++ b/libraries/pico_vector/af-file-io.h @@ -0,0 +1,20 @@ +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +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); +#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 new file mode 100644 index 000000000..f522d2cc9 --- /dev/null +++ b/libraries/pico_vector/af-memory.h @@ -0,0 +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, ...); +#ifdef __cplusplus +} +#endif \ 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..c88826f05 --- /dev/null +++ b/libraries/pico_vector/alright-fonts.h @@ -0,0 +1,458 @@ +/* + + 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 +#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 + +#ifndef AF_DEBUG + #define AF_DEBUG(...) +#endif + +#include "pretty-poly.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + int8_t x, y; +} af_point_t; +pp_point_t af_point_transform(pp_point_t *p, pp_mat3_t *m); + +typedef struct { + uint8_t point_count; + af_point_t *points; +} af_path_t; + +typedef struct { + char codepoint; + int8_t x, y, w, h; + int8_t advance; + uint8_t path_count; + af_path_t *paths; +} af_glyph_t; + +typedef struct { + uint16_t flags; + uint16_t glyph_count; + af_glyph_t *glyphs; +} af_face_t; + +typedef enum { + AF_H_ALIGN_LEFT = 0, AF_H_ALIGN_CENTER = 1, AF_H_ALIGN_RIGHT = 2, + AF_H_ALIGN_JUSTIFY = 4, + AF_V_ALIGN_TOP = 8, AF_V_ALIGN_MIDDLE = 16, AF_V_ALIGN_BOTTOM = 32 +} af_align_t; + +typedef struct { + af_face_t *face; // font + float size; // text size in pixels + float line_height; // spacing between lines (%) + float letter_spacing; // spacing between characters (%) + float word_spacing; // spacing between words (%) + 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, 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 + +#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); + + 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 + 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 *)(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; + 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 += 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 += path->point_count; + } + } + + // load the glyph points + for(int i = 0; i < glyph_count; i++) { + af_glyph_t *glyph = &face->glyphs[i]; + for(int j = 0; j < glyph->path_count; j++) { + af_path_t *path = &glyph->paths[j]; + for(int k = 0; k < path->point_count; k++) { + af_point_t *point = &path->points[k]; + point->x = ru8(file); + point->y = ru8(file); + } + } + } + + return true; +} + +af_glyph_t *find_glyph(af_face_t *face, char c) { + for(int i = 0; i < face->glyph_count; i++) { + if(face->glyphs[i].codepoint == c) { + return &face->glyphs[i]; + } + } + return NULL; +} + +void af_render_glyph(af_glyph_t* glyph, af_text_metrics_t *tm) { + assert(glyph != NULL); + + pp_poly_t *poly = 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_poly_free(poly); +} + +void af_render_character(af_face_t *face, const char c, af_text_metrics_t *tm) { + af_glyph_t *glyph = find_glyph(face, c); + if(!glyph) { + return; + } + af_render_glyph(glyph, tm); +} + +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' ') { + char_width = (glyph->advance * tm->word_spacing) / 100.0f; + last_space = text; + } else { + 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; +} + +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; +} + +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) { + 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); + } + + return max_width; +} + +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; + + pp_mat3_t *old = pp_transform(NULL); + + 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 + 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 && 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; + + 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_mat3_t final_transform = *old; + pp_mat3_mul(&final_transform, &caret_transform); + pp_transform(&final_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; + } + + } + + line += 1; // Skip over \n + line_len = line_length(line, tend); + + caret.x = 0; + caret.y += line_height; + } + + + + pp_transform(old); +} + +void _af_render(af_face_t *face, const char *text, af_text_metrics_t *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, float max_line_width, af_text_metrics_t *tm) { + pp_rect_t result; + bool first = true; + 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 + 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; + + 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; + } + + } + + line += 1; // Skip over \n + line_len = line_length(line, tend); + + caret.x = 0; + caret.y += line_height; + } + + return result; +} + +#endif // AF_IMPLEMENTATION + +#endif // AF_INCLUDE_H \ 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 a679c97b9..000000000 --- a/libraries/pico_vector/alright_fonts.cpp +++ /dev/null @@ -1,181 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#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) { - 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, pretty_poly::point_t origin) { - 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); - } - } - - 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 - */ - - // 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); - - 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 - pretty_poly::point_t *points = new pretty_poly::point_t[count]; - ifs.read((char *)points, count * 2); - - g.contours.push_back({points, 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 0590a59c9..000000000 --- a/libraries/pico_vector/alright_fonts.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "pretty_poly.hpp" - -namespace alright_fonts { - - struct glyph_t { - uint16_t codepoint; - pretty_poly::rect_t bounds; - uint8_t advance; - std::vector> contours; - }; - - struct face_t { - uint16_t glyph_count; - uint16_t flags; - std::map glyphs; - - face_t() {}; - face_t(pretty_poly::file_io &ifs) {load(ifs);} - face_t(std::string_view path) {load(path);} - - bool load(pretty_poly::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 - pretty_poly::antialias_t antialiasing = pretty_poly::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 - */ - pretty_poly::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); -} \ 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..3a3af2d5e 100644 --- a/libraries/pico_vector/pico_vector.cmake +++ b/libraries/pico_vector/pico_vector.cmake @@ -1,9 +1,19 @@ +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 + ${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}) -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) + +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/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp index 5c36238d7..43079a493 100644 --- a/libraries/pico_vector/pico_vector.cpp +++ b/libraries/pico_vector/pico_vector.cpp @@ -1,192 +1,72 @@ +#define PP_IMPLEMENTATION +#define AF_IMPLEMENTATION +#define PPP_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; - } - - 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; - } - - 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; - } - - i = next_break + 1; + void PicoVector::transform(pp_path_t *path, pp_mat3_t *t) { + for (auto j = 0; j < path->count; j++) { + path->points[j] = pp_point_transform(&path->points[j], 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); - - // Align text from the bottom left - caret.y += text_metrics.line_height; - caret *= transform; + 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); + } - pretty_poly::point_t space; - pretty_poly::point_t carriage_return(0, -text_metrics.line_height); + 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); + } - space.x = alright_fonts::measure_character(text_metrics, ' ').w; - if (space.x == 0) { - space.x = text_metrics.word_spacing; + void PicoVector::transform(pp_poly_t *poly, pp_mat3_t *t) { + pp_path_t *path = poly->paths; + while(path) { + transform(path, t); + path = path->next; } + } - space *= transform; - carriage_return *= transform; - const pretty_poly::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 += 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 -= carriage_return; - carriage_return = initial_carriage_return; - } + 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}; - for(size_t j = i; j < std::min(next_break + 1, text.length()); j++) { - if (text[j] == '\n') { // Linebreak - caret -= carriage_return; - carriage_return = initial_carriage_return; - } else if (text[j] == ' ') { // Space - caret += space; - 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); - } - 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; - } + text_metrics.transform = t; - i = next_break + 1; - } + af_render(text_metrics.face, text.data(), text.size(), max_width, max_height, &text_metrics); - return Point(caret.x, caret.y); + return caret; } } \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index 8502e5fb3..5808f29c8 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -1,7 +1,28 @@ -#include "pretty_poly.hpp" -#include "alright_fonts.hpp" + +#include "af-file-io.h" +#include "af-memory.h" + +#define AF_FILE void* +#define AF_FREAD(p, size, nmemb, stream) fileio_read(stream, p, nmemb) +#define AF_FGETC(stream) fileio_getc(stream) + +#define AF_MALLOC(size) af_malloc(size) +#define AF_REALLOC(p, size) af_realloc(p, size) +#define AF_FREE(p) af_free(p) + +#define PP_MALLOC(size) af_malloc(size) +#define PP_REALLOC(p, size) af_realloc(p, size) +#define PP_FREE(p) af_free(p) + +#define AF_DEBUG(...) af_debug(__VA_ARGS__) + +#include "pretty-poly.h" +#include "pretty-poly-primitives.h" +#include "alright-fonts.h" #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 @@ -9,79 +30,124 @@ namespace pimoroni { class PicoVector { private: - PicoGraphics *graphics; - alright_fonts::text_metrics_t text_metrics; - const uint8_t alpha_map[4] {0, 128, 192, 255}; + af_text_metrics_t text_metrics; 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); - } - } - 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.bounds.w; - } - } - }, graphics->supports_alpha_blend() ? pretty_poly::X4 : pretty_poly::NONE, {graphics->clip.x, graphics->clip.y, graphics->clip.w, graphics->clip.h}); + static PicoGraphics *graphics; + PicoVector(PicoGraphics *graphics, void *mem = nullptr) { + PicoVector::graphics = graphics; + + pp_tile_callback(PicoVector::tile_callback); + + 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); + + 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) { + // 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(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) { - text_metrics.set_size(font_size); + 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; + } + + 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(), 0, &text_metrics); } bool set_font(std::string_view font_path, unsigned int font_size) { - bool result = text_metrics.face.load(font_path); + if(text_metrics.face) { + af_free(text_metrics.face->glyphs); + af_free(text_metrics.face); + } + text_metrics.face = (af_face_t *)af_malloc(sizeof(af_face_t)); + //bool result = text_metrics.face.load(font_path); + void* font = fileio_open(font_path.data()); + bool result = af_load_font_file(font, text_metrics.face); set_font_size(font_size); return result; } - void rotate(std::vector> &contours, Point origin, float angle); - void translate(std::vector> &contours, Point translation); + 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); - void rotate(pretty_poly::contour_t &contour, Point origin, float angle); - void translate(pretty_poly::contour_t &contour, Point translation); + set_font_size(font_size); - Point text(std::string_view text, Point origin); - Point text(std::string_view text, Point origin, float angle); + return result; + } - void polygon(std::vector> contours, Point origin = Point(0, 0), int scale=65536); + 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); + + 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 translate(pp_path_t *path, pp_point_t translation); + void translate(pp_poly_t *poly, pp_point_t translation); + + void draw(pp_poly_t *poly); + void draw(pp_poly_t *poly, pp_mat3_t *t); + + void draw(pp_path_t *path) { + 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 = 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() { - return pretty_poly::buffer_size(); + return 0; }; }; } \ No newline at end of file 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 new file mode 100644 index 000000000..c92abec45 --- /dev/null +++ b/libraries/pico_vector/pretty-poly.h @@ -0,0 +1,676 @@ +/* + + 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 +#include + +#ifndef PP_COORD_TYPE +#define PP_COORD_TYPE float +#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 64 +#endif + +#ifndef PP_SCALE_TO_ALPHA +#define PP_SCALE_TO_ALPHA 1 +#endif + +#if defined(PICO_ON_DEVICE) && PICO_ON_DEVICE +#define USE_RP2040_INTERP +#include "hardware/interp.h" +#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_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; + uint32_t stride; + uint8_t *data; +} pp_tile_t; + +typedef struct _pp_path_t { + pp_point_t *points; + 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); +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_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); +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); + +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); + + +#ifdef __cplusplus +} +#endif + +#ifdef PP_IMPLEMENTATION + +#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; + +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) * 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; +} + +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 + 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++; +} + +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->storage = path->count + count; + path->points = (pp_point_t *)PP_REALLOC(path->points, sizeof(pp_point_t) * (path->storage)); + } + 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) { + 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}; +} + +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){}; + 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; +} + +// buffer that each tile is rendered into before callback +// allocate one extra byte to allow a small optimization in the row renderer +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 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}; +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}; +} + +void pp_tile_callback(pp_tile_callback_t callback) { + _pp_tile_callback = callback; +} + +// maximum tile bounds determined by antialias level +void pp_antialias(pp_antialias_t antialias) { + _pp_antialias = antialias; +} + +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, pp_rect_t *tb) { + 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 gradient + 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)(tb->h << _pp_antialias), ey) - y; + + 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 +// pp_nodes[y][pp_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, (tb->w << _pp_antialias)), 0); + //debug(" + adding node at %d, %d\n", x, y); + // add node to node list + pp_nodes[y][pp_node_counts[y]++] = nx; + + // step to next scanline and accumulate error + y++; + e += einc; + } +//#endif +} + +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){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 = 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]; + 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, tb); + last = next; + } +} + +int compare_nodes(const void* a, const void* b) { + return *((int*)a) - *((int*)b); +} + +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); + + for(int y = 0; y < ((int)PP_TILE_BUFFER_SIZE << _pp_antialias); y++) { + + // debug(" : row %d node count %d\n", y, pp_node_counts[y]); + + if(pp_node_counts[y] == 0) continue; // no nodes on this raster line + + qsort(&pp_nodes[y][0], pp_node_counts[y], sizeof(int), compare_nodes); + + unsigned char* row_data = &pp_tile_buffer[(y >> _pp_antialias) * PP_TILE_BUFFER_SIZE]; + + 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; + } + + // 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); + + // rasterise the span into the tile buffer + do { + row_data[sx >> _pp_antialias]++; + } while(++sx < ex); + } + } + + rb.w = maxx - minx; + + // 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); + } + + 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 = &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++; + } + } + #endif + + debug(" : rendered tile bounds %d, %d (%d x %d)\n", rb.x, rb.y, rb.w, rb.h); + + return rb; +} + +void pp_render(pp_poly_t *polygon) { + + debug("> draw polygon with %u contours\n", pp_poly_path_count(polygon)); + + if(!polygon->paths) return; + + // determine extreme bounds + pp_rect_t pb = pp_poly_bounds(polygon); + + if(_pp_transform) { + pb = pp_rect_transform(&pb, _pp_transform); + } + + 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 + 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 = 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); + + // 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(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; + 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 + + pp_rect_t rb = render_nodes(&tb); + tb.x += rb.x; tb.y += rb.y; tb.w = rb.w; tb.h = 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 = PP_TILE_BUFFER_SIZE, + .data = pp_tile_buffer + rb.x + (PP_TILE_BUFFER_SIZE * 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.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); - } - }; - -} 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/picographics/picographics.c b/micropython/modules/picographics/picographics.c index 0074b55f0..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); @@ -24,8 +26,12 @@ 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_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); @@ -60,10 +66,17 @@ 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) }, + + { 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) }, { 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) }, @@ -158,6 +171,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 a55e8ad41..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; @@ -248,30 +249,37 @@ 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; } 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 +288,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 +297,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 +313,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; @@ -386,7 +396,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); @@ -395,7 +406,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 +429,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 +464,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 +495,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 +580,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 +621,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); @@ -626,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); /* @@ -649,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; } @@ -678,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; @@ -755,6 +788,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); @@ -872,6 +913,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 655d336b7..f1313ff1b 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 { @@ -71,12 +72,18 @@ 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); 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); @@ -85,6 +92,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); diff --git a/micropython/modules/picovector/micropython.cmake b/micropython/modules/picovector/micropython.cmake index c513cdcc5..f5f65cea9 100644 --- a/micropython/modules/picovector/micropython.cmake +++ b/micropython/modules/picovector/micropython.cmake @@ -2,8 +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 ) @@ -21,3 +19,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" +) diff --git a/micropython/modules/picovector/picovector.c b/micropython/modules/picovector/picovector.c index a67a7072b..5e025c657 100644 --- a/micropython/modules/picovector/picovector.c +++ b/micropython/modules/picovector/picovector.c @@ -3,19 +3,46 @@ /* 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, @@ -25,68 +52,66 @@ MP_DEFINE_CONST_OBJ_TYPE( 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_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) }, +}; + +static MP_DEFINE_CONST_DICT(TRANSFORM_locals_dict, TRANSFORM_locals_dict_table); + MP_DEFINE_CONST_OBJ_TYPE( - REGULAR_POLYGON_type, - MP_QSTR_regular_polygon, - MP_TYPE_FLAG_NONE, - make_new, REGULAR_POLYGON_make_new, - locals_dict, (mp_obj_dict_t*)&POLYGON_locals_dict -); -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 = POLYGON_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 */ 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); -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_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_text), MP_ROM_PTR(&VECTOR_text_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_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 +119,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,11 +126,19 @@ 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) }, + { 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); @@ -123,8 +148,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 a77a38b2c..500f8621c 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; @@ -24,15 +25,66 @@ typedef struct _VECTOR_obj_t { PicoVector *vector; } _VECTOR_obj_t; -typedef struct _POLYGON_obj_t { +typedef struct _TRANSFORM_obj_t { mp_obj_base_t base; - pretty_poly::contour_t contour; -} _POLYGON_obj_t; + pp_mat3_t transform; +} _TRANSFORM_obj_t; -pretty_poly::file_io::file_io(std::string_view filename) { - mp_obj_t fn = mp_obj_new_str(filename.data(), (mp_uint_t)filename.size()); +typedef struct _POLY_obj_t { + mp_obj_base_t base; + pp_poly_t *poly; +} _POLY_obj_t; + +#if DEBUG +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; +} +#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); + //__printf_debug_flush(); + //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 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_free(p); +} - //mp_printf(&mp_plat_print, "Opening file %s\n", filename.data()); +void* fileio_open(const char *filename) { + mp_obj_t fn = mp_obj_new_str(filename, (mp_uint_t)strlen(filename)); + + //mp_printf(&mp_plat_print, "Opening file %s\n", filename); + //__printf_debug_flush(); mp_obj_t args[2] = { fn, @@ -41,36 +93,44 @@ pretty_poly::file_io::file_io(std::string_view filename) { // Stat the file to get its size // example tuple response: (32768, 0, 0, 0, 0, 0, 5153, 1654709815, 1654709815, 1654709815) - mp_obj_t stat = mp_vfs_stat(fn); - mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR2(stat, mp_obj_tuple_t); - filesize = mp_obj_get_int(tuple->items[6]); + //mp_obj_t stat = mp_vfs_stat(fn); + //mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR2(stat, mp_obj_tuple_t); + //int filesize = mp_obj_get_int(tuple->items[6]); + //mp_printf(&mp_plat_print, "Size %lu\n", filesize); mp_obj_t fhandle = mp_vfs_open(MP_ARRAY_SIZE(args), &args[0], (mp_map_t *)&mp_const_empty_map); - this->state = (void *)fhandle; + return (void*)fhandle; } -pretty_poly::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 pretty_poly::file_io::read(void *buf, size_t len) { +size_t fileio_read(void* fhandle, void *buf, size_t len) { //mp_printf(&mp_plat_print, "Reading %lu bytes\n", len); - mp_obj_t fhandle = this->state; + //__printf_debug_flush(); int error; - return mp_stream_read_exactly(fhandle, buf, len, &error); + return mp_stream_read_exactly((mp_obj_t)fhandle, buf, len, &error); +} + +int fileio_getc(void* fhandle) { + unsigned char buf; + //mp_printf(&mp_plat_print, "Reading char\n"); + //__printf_debug_flush(); + fileio_read(fhandle, (void *)&buf, 1); + return (int)buf; } -size_t pretty_poly::file_io::tell() { - mp_obj_t fhandle = this->state; +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 +138,16 @@ size_t pretty_poly::file_io::tell() { return seek_s.offset; } -bool pretty_poly::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) { - mp_obj_t fhandle = this->state; +// Re-implementation of stream.c/STATIC mp_obj_t stream_seek(size_t n_args, const mp_obj_t *args) +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); } @@ -100,6 +155,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,196 +163,370 @@ 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 */ -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_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_, 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); - _POLYGON_obj_t *self = mp_obj_malloc_with_finaliser(_POLYGON_obj_t, &POLYGON_type); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _POLY_obj_t); - 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); + picovector_point_type s = args[ARG_stroke].u_obj == mp_const_none ? 0 : mp_picovector_get_point_type(args[ARG_stroke].u_obj); + + picovector_point_type r1 = 0; + picovector_point_type r2 = 0; + picovector_point_type r3 = 0; + picovector_point_type r4 = 0; - self->contour.points = m_new(pretty_poly::point_t, 4); - self->contour.count = 4; + 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); - 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)}; + 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_x, MP_ARG_REQUIRED | MP_ARG_INT }, - { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_INT }, - { MP_QSTR_sides, MP_ARG_REQUIRED | MP_ARG_INT }, + { 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_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); + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - _POLYGON_obj_t *self = mp_obj_malloc_with_finaliser(_POLYGON_obj_t, &POLYGON_type); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _POLY_obj_t); - 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); - } - int o_x = args[ARG_x].u_int; - int o_y = args[ARG_y].u_int; - 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->contour.points = m_new(pretty_poly::point_t, sides); - self->contour.count = sides; - - for(auto s = 0u; s < sides; s++) { - float current_angle = angle * s + rotation; - self->contour.points[s] = { - (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) { - _POLYGON_obj_t *self = mp_obj_malloc_with_finaliser(_POLYGON_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); - if(num_points < 3) mp_raise_ValueError("Polygon: At least 3 points required."); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _POLY_obj_t); - self->contour.points = m_new(pretty_poly::point_t, num_points); - self->contour.count = num_points; + 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); - self->contour.points[i] = { - (picovector_point_type)mp_obj_get_int(t_point->items[0]), - (picovector_point_type)mp_obj_get_int(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) { - _POLYGON_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLYGON_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; - pretty_poly::point_t sum(0, 0); + // TODO: Maybe include in pretty-poly? + // Might need to handle multiple paths + pp_path_t *path = self->poly->paths; - for(auto i = 0u; i < self->contour.count; i++) { - sum += self->contour.points[i]; + for(auto i = 0; i < path->count; i++) { + sum_x += path->points[i].x; + sum_y += path->points[i].y; } - sum /= (float)self->contour.count; + sum_x /= (float)path->count; + sum_y /= (float)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); } mp_obj_t POLYGON_bounds(mp_obj_t self_in) { - _POLYGON_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLYGON_obj_t); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLY_obj_t); + + pp_rect_t bounds = pp_poly_bounds(self->poly); 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_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); } -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); +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); + } +} + +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_obj_new_int(self->contour.count), PRINT_REPR); - mp_print_str(print, ", bounds = "); - mp_obj_print_helper(print, mp_obj_new_int(self->contour.bounds().x), PRINT_REPR); - mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_obj_new_int(self->contour.bounds().y), PRINT_REPR); - mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_obj_new_int(self->contour.bounds().w), PRINT_REPR); - mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_obj_new_int(self->contour.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) { - _POLYGON_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLYGON_obj_t); - (void)self; - // 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; - size_t cur; + pp_path_t *cur; } mp_obj_polygon_it_t; -static mp_obj_t py_image_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); - _POLYGON_obj_t *polygon = MP_OBJ_TO_PTR2(self->polygon, _POLYGON_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 >= polygon->contour.count) return MP_OBJ_STOP_ITERATION; + if(!self->cur) 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)); + 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 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_image_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_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(); + 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) { @@ -317,21 +547,43 @@ 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); 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); 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 + GET_STR_DATA_LEN(font, str, str_len); + result = self->vector->set_font((const char*)str, font_size); } else { @@ -347,20 +599,41 @@ 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_antialiasing(mp_obj_t self_in, mp_obj_t aa) { +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); - self->vector->set_antialiasing((pretty_poly::antialias_t)mp_obj_get_int(aa)); + self->vector->set_font_word_spacing(mp_obj_get_int(spacing)); return mp_const_none; } -mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +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); + + 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); + + 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_REQUIRED | MP_ARG_INT }, - { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_INT }, + { 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} } }; @@ -375,55 +648,72 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) GET_STR_DATA_LEN(text_obj, str, str_len); - const std::string_view t((const char*)str, str_len); + const std::string_view t((const char *)str, str_len); int x = args[ARG_x].u_int; int y = args[ARG_y].u_int; - if(args[ARG_angle].u_obj == mp_const_none) { - 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)); } - return mp_const_none; + 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_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_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); - 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); + 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; - _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _VECTOR_obj_t); + 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(!MP_OBJ_IS_TYPE(args[ARG_polygon].u_obj, &POLYGON_type)) mp_raise_TypeError("rotate: polygon required"); + if(t_clip->len != 4) mp_raise_ValueError("Clip must have x, y, w, h"); - _POLYGON_obj_t *poly = MP_OBJ_TO_PTR2(args[ARG_polygon].u_obj, _POLYGON_obj_t); + 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]); + } - Point origin = Point(args[ARG_origin_x].u_int, args[ARG_origin_y].u_int); + pp_clip(x, y, w, h); - float angle = mp_obj_get_float(args[ARG_angle].u_obj); + return mp_const_none; +} - self->vector->rotate(poly->contour, origin, angle); +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((pp_antialias_t)mp_obj_get_int(aa)); 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 }; +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, ARG_max_width, ARG_max_height }; 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_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_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)]; @@ -431,36 +721,41 @@ mp_obj_t VECTOR_translate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ _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"); + mp_obj_t text_obj = args[ARG_text].u_obj; - _POLYGON_obj_t *poly = MP_OBJ_TO_PTR2(args[ARG_polygon].u_obj, _POLYGON_obj_t); + if(!mp_obj_is_str_or_bytes(text_obj)) mp_raise_TypeError("text: string required"); - Point translate = Point(args[ARG_x].u_int, args[ARG_y].u_int); + GET_STR_DATA_LEN(text_obj, str, str_len); - self->vector->translate(poly->contour, translate); + const std::string_view t((const char *)str, str_len); - return mp_const_none; -} + 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; -mp_obj_t VECTOR_draw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + pp_mat3_t tt = pp_mat3_identity(); - size_t num_polygons = n_args - 1; - const mp_obj_t *polygons = pos_args + 1; + if(args[ARG_angle].u_obj != mp_const_none) { + pp_mat3_rotate(&tt, mp_obj_get_float(args[ARG_angle].u_obj)); + } - _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(pos_args[0], _VECTOR_obj_t); + pp_mat3_translate(&tt, (float)x, (float)y); - std::vector> contours; + self->vector->text(t, max_width, max_height, &tt); - for(auto i = 0u; i < num_polygons; i++) { - mp_obj_t poly_obj = polygons[i]; + return mp_const_none; +} + +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; - if(!MP_OBJ_IS_TYPE(poly_obj, &POLYGON_type)) mp_raise_TypeError("draw: Polygon required."); + if(!MP_OBJ_IS_TYPE(poly_in, &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); - } + _POLY_obj_t *poly = MP_OBJ_TO_PTR2(poly_in, _POLY_obj_t); - self->vector->polygon(contours); + pp_render(poly->poly); return mp_const_none; } diff --git a/micropython/modules/picovector/picovector.h b/micropython/modules/picovector/picovector.h index 899f1ae47..cff667612 100644 --- a/micropython/modules/picovector/picovector.h +++ b/micropython/modules/picovector/picovector.h @@ -3,26 +3,48 @@ 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 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_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 */ + 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); -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