diff --git a/libraries/pico_graphics/pico_graphics.cpp b/libraries/pico_graphics/pico_graphics.cpp index 557fc785..c7e775b8 100644 --- a/libraries/pico_graphics/pico_graphics.cpp +++ b/libraries/pico_graphics/pico_graphics.cpp @@ -8,6 +8,7 @@ namespace pimoroni { int PicoGraphics::reset_pen(uint8_t i) {return -1;}; int PicoGraphics::create_pen(uint8_t r, uint8_t g, uint8_t b) {return -1;}; int PicoGraphics::create_pen_hsv(float h, float s, float v){return -1;}; + void PicoGraphics::set_pixel_alpha(const Point &p, const uint8_t a) {}; void PicoGraphics::set_pixel_dither(const Point &p, const RGB &c) {}; void PicoGraphics::set_pixel_dither(const Point &p, const RGB565 &c) {}; void PicoGraphics::set_pixel_dither(const Point &p, const uint8_t &c) {}; @@ -16,6 +17,7 @@ namespace pimoroni { int PicoGraphics::get_palette_size() {return 0;} RGB* PicoGraphics::get_palette() {return nullptr;} + bool PicoGraphics::supports_alpha_blend() {return false;} 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 2dd7c83d..40f72977 100644 --- a/libraries/pico_graphics/pico_graphics.hpp +++ b/libraries/pico_graphics/pico_graphics.hpp @@ -47,7 +47,19 @@ namespace pimoroni { g((c >> 8) & 0xff), b(c & 0xff) {} constexpr RGB(int16_t r, int16_t g, int16_t b) : r(r), g(g), b(b) {} - + + constexpr uint8_t blend(uint8_t s, uint8_t d, uint8_t a) { + return d + ((a * (s - d) + 127) >> 8); + } + + constexpr RGB blend(RGB with, const uint8_t alpha) { + return RGB( + blend(with.r, r, alpha), + blend(with.g, g, alpha), + blend(with.b, b, alpha) + ); + } + static RGB from_hsv(float h, float s, float v) { float i = floor(h * 6.0f); float f = h * 6.0f - i; @@ -268,6 +280,7 @@ namespace pimoroni { virtual int get_palette_size(); virtual RGB* get_palette(); + virtual bool supports_alpha_blend(); virtual int create_pen(uint8_t r, uint8_t g, uint8_t b); virtual int create_pen_hsv(float h, float s, float v); @@ -276,6 +289,7 @@ namespace pimoroni { virtual void set_pixel_dither(const Point &p, const RGB &c); virtual void set_pixel_dither(const Point &p, const RGB565 &c); virtual void set_pixel_dither(const Point &p, const uint8_t &c); + virtual void set_pixel_alpha(const Point &p, const uint8_t a); 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); @@ -471,6 +485,9 @@ namespace pimoroni { void set_pixel_span(const Point &p, uint l) override; void set_pixel_dither(const Point &p, const RGB &c) override; void set_pixel_dither(const Point &p, const RGB565 &c) override; + void set_pixel_alpha(const Point &p, const uint8_t a) override; + + bool supports_alpha_blend() override {return true;} void sprite(void* data, const Point &sprite, const Point &dest, const int scale, const int transparent) override; diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp index 668e484d..1bce808f 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp @@ -34,6 +34,15 @@ namespace pimoroni { *buf++ = color; } } + void PicoGraphics_PenRGB332::set_pixel_alpha(const Point &p, const uint8_t a) { + if(!bounds.contains(p)) return; + + uint8_t *buf = (uint8_t *)frame_buffer; + + RGB332 blended = RGB(buf[p.y * bounds.w + p.x]).blend(RGB(color), a).to_rgb332(); + + buf[p.y * bounds.w + p.x] = blended; + }; void PicoGraphics_PenRGB332::set_pixel_dither(const Point &p, const RGB &c) { if(!bounds.contains(p)) return; uint8_t _dmv = dither16_pattern[(p.x & 0b11) | ((p.y & 0b11) << 2)]; diff --git a/libraries/pico_vector/alright_fonts.cpp b/libraries/pico_vector/alright_fonts.cpp new file mode 100644 index 00000000..9a6a9131 --- /dev/null +++ b/libraries/pico_vector/alright_fonts.cpp @@ -0,0 +1,171 @@ +#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); + } + } + + void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t origin, pretty_poly::mat3_t transform) { + if(tm.face.glyphs.count(codepoint) == 1) { + glyph_t glyph = tm.face.glyphs[codepoint]; + + // scale is a fixed point 16:16 value, our font data is already scaled to + // -128..127 so to get the pixel size we want we can just shift the + // users requested size up one bit + unsigned scale = tm.size << 9; + + std::vector> contours; + + for(auto i = 0u; i < glyph.contours.size(); i++) { + unsigned int count = glyph.contours[i].count; + point_t *points = (point_t *)malloc(sizeof(point_t) * 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); + } + + pretty_poly::draw_polygon(contours, origin, scale); + + for(auto contour : contours) { + free(contour.points); + } + } + } + + /* + 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 new file mode 100644 index 00000000..0bff4629 --- /dev/null +++ b/libraries/pico_vector/alright_fonts.hpp @@ -0,0 +1,74 @@ +#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); + void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t origin, pretty_poly::mat3_t transform); +} \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.cmake b/libraries/pico_vector/pico_vector.cmake new file mode 100644 index 00000000..cecbff4f --- /dev/null +++ b/libraries/pico_vector/pico_vector.cmake @@ -0,0 +1,9 @@ +add_library(pico_vector + ${CMAKE_CURRENT_LIST_DIR}/pico_vector.cpp + ${CMAKE_CURRENT_LIST_DIR}/pretty_poly.cpp + ${CMAKE_CURRENT_LIST_DIR}/alright_fonts.cpp +) + +target_include_directories(pico_vector INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(pico_vector pico_stdlib) \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp new file mode 100644 index 00000000..0e13d60d --- /dev/null +++ b/libraries/pico_vector/pico_vector.cpp @@ -0,0 +1,188 @@ +#include "pico_vector.hpp" +#include + +namespace pimoroni { + void PicoVector::polygon(std::vector> contours, Point origin, int scale) { + pretty_poly::draw_polygon( + contours, + pretty_poly::point_t(origin.x, origin.y), + scale); + } + + void PicoVector::rotate(std::vector> &contours, Point origin, float angle) { + pretty_poly::mat3_t t2 = pretty_poly::mat3_t::translation(origin.x, origin.y); + pretty_poly::mat3_t t1 = pretty_poly::mat3_t::translation(-origin.x, -origin.y); + angle = 2 * M_PI * (angle / 360.0f); + pretty_poly::mat3_t r = pretty_poly::mat3_t::rotation(angle); + for(auto &contour : contours) { + for(auto i = 0u; i < contour.count; i++) { + contour.points[i] *= t1; + contour.points[i] *= r; + contour.points[i] *= t2; + } + } + } + + void PicoVector::translate(std::vector> &contours, Point translation) { + pretty_poly::mat3_t t = pretty_poly::mat3_t::translation(translation.x, translation.y); + for(auto &contour : contours) { + for(auto i = 0u; i < contour.count; i++) { + contour.points[i] *= t; + } + } + } + + void PicoVector::rotate(pretty_poly::contour_t &contour, Point origin, float angle) { + pretty_poly::mat3_t t2 = pretty_poly::mat3_t::translation(origin.x, origin.y); + pretty_poly::mat3_t t1 = pretty_poly::mat3_t::translation(-origin.x, -origin.y); + angle = 2 * M_PI * (angle / 360.0f); + pretty_poly::mat3_t r = pretty_poly::mat3_t::rotation(angle); + for(auto i = 0u; i < contour.count; i++) { + contour.points[i] *= t1; + contour.points[i] *= r; + contour.points[i] *= t2; + } + } + + void PicoVector::translate(pretty_poly::contour_t &contour, Point translation) { + pretty_poly::mat3_t t = pretty_poly::mat3_t::translation(translation.x, translation.y); + for(auto i = 0u; i < contour.count; i++) { + contour.points[i] *= t; + } + } + + Point PicoVector::text(std::string_view text, Point origin) { + // 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; + } + + return Point(caret.x, caret.y); + } + + Point PicoVector::text(std::string_view text, Point origin, float angle) { + // 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 * M_PI * (angle / 360.0f); + pretty_poly::mat3_t transform = pretty_poly::mat3_t::rotation(angle); + + // Align text from the bottom left + caret.y += text_metrics.line_height; + caret *= transform; + + pretty_poly::point_t space; + pretty_poly::point_t carriage_return(0, text_metrics.line_height); + + space.x = alright_fonts::measure_character(text_metrics, ' ').w; + if (space.x == 0) { + space.x = text_metrics.word_spacing; + } + + space *= transform; + carriage_return *= transform; + + 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.x = 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.x = 0; + } 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; + } + + i = next_break + 1; + } + + return Point(caret.x, caret.y); + } +} \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp new file mode 100644 index 00000000..1cb3a1cc --- /dev/null +++ b/libraries/pico_vector/pico_vector.hpp @@ -0,0 +1,81 @@ +#include "pretty_poly.hpp" +#include "alright_fonts.hpp" +#include "pico_graphics.hpp" + +namespace pimoroni { + + // Integer point types cause compound error in transformations + typedef float picovector_point_type; + + class PicoVector { + private: + PicoGraphics *graphics; + alright_fonts::text_metrics_t text_metrics; + const uint8_t alpha_map[4] {0, 128, 192, 255}; + + public: + PicoVector(PicoGraphics *graphics, void *mem = nullptr) : graphics(graphics) { + pretty_poly::init(mem); + + set_options([this](const pretty_poly::tile_t &tile) -> void { + uint8_t *tile_data = tile.data; + + if(this->graphics->supports_alpha_blend() && pretty_poly::settings::antialias != pretty_poly::NONE) { + 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, {0, 0, graphics->bounds.w, graphics->bounds.h}); + } + + void set_antialiasing(pretty_poly::antialias_t antialias) { + set_options(pretty_poly::settings::callback, antialias, pretty_poly::settings::clip); + } + + void set_font_size(unsigned int font_size) { + text_metrics.set_size(font_size); + } + + bool set_font(std::string_view font_path, unsigned int font_size) { + bool result = text_metrics.face.load(font_path); + + set_font_size(font_size); + + return result; + } + + void rotate(std::vector> &contours, Point origin, float angle); + void translate(std::vector> &contours, Point translation); + + void rotate(pretty_poly::contour_t &contour, Point origin, float angle); + void translate(pretty_poly::contour_t &contour, Point translation); + + Point text(std::string_view text, Point origin); + Point text(std::string_view text, Point origin, float angle); + + void polygon(std::vector> contours, Point origin = Point(0, 0), int scale=65536); + + static constexpr size_t pretty_poly_buffer_size() { + return pretty_poly::buffer_size(); + }; + }; +} \ No newline at end of file diff --git a/libraries/pico_vector/pretty_poly.cpp b/libraries/pico_vector/pretty_poly.cpp new file mode 100644 index 00000000..78617ca6 --- /dev/null +++ b/libraries/pico_vector/pretty_poly.cpp @@ -0,0 +1,239 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "pretty_poly.hpp" + + +#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); + } + + /*sx <<= settings::antialias; + ex <<= settings::antialias; + sy <<= settings::antialias; + ey <<= settings::antialias;*/ + + int x = sx; + int y = sy; + int e = 0; + + int xinc = sign(ex - sx); + int einc = abs(ex - sx) + 1; + + // todo: preclamp sy and ey (and therefore count) no need to perform + // that test inside the loop + int dy = ey - sy; + int count = dy; + debug(" + line segment from %d, %d to %d, %d\n", sx, sy, ex, ey); + // loop over scanlines + while(count--) { + // consume accumulated error + while(e > dy) {e -= dy; x += xinc;} + + if(y >= 0 && y < (int)node_buffer_size) { + // clamp node x value to tile bounds + int nx = std::max(std::min(x, (int)(tile_bounds.w << settings::antialias)), 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) { + for(auto y = 0; y < (int)node_buffer_size; y++) { + if(node_counts[y] == 0) { + continue; + } + + std::sort(&nodes[y][0], &nodes[y][0] + node_counts[y]); + + 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; + } + + debug(" - render span at %d from %d to %d\n", y, sx, ex); + + for(int x = sx; x < ex; x++) { + tile.data[(x >> settings::antialias) + (y >> settings::antialias) * tile.stride]++; + } + } + } + } + + 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(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); + + + 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(contour_t &contour : contours) { + debug(" : build nodes for contour\n"); + build_nodes(contour, tile, origin, scale); + } + + debug(" : render the tile\n"); + // render the tile + render_nodes(tile); + + settings::callback(tile); + } + } + } +} + +template void pretty_poly::draw_polygon(std::vector> contours, point_t origin, int scale); +template void pretty_poly::draw_polygon(std::vector> contours, point_t origin, int scale); +template void pretty_poly::draw_polygon(std::vector> contours, point_t origin, int scale); +template void pretty_poly::draw_polygon(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 new file mode 100644 index 00000000..d1ee2430 --- /dev/null +++ b/libraries/pico_vector/pretty_poly.hpp @@ -0,0 +1,72 @@ +#pragma once +#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); + + template + void draw_polygon(T *points, unsigned count); + + template + void draw_polygon(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 new file mode 100644 index 00000000..51076f8a --- /dev/null +++ b/libraries/pico_vector/pretty_poly_types.hpp @@ -0,0 +1,132 @@ +#pragma once +#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, v10, v20, v01, v11, v21, v02, v12, 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;} + }; + + // 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 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); + } + + }; + + 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(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() { + 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/picow_explorer/vector_clock.py b/micropython/examples/picow_explorer/vector_clock.py new file mode 100644 index 00000000..4a3c7d78 --- /dev/null +++ b/micropython/examples/picow_explorer/vector_clock.py @@ -0,0 +1,119 @@ +import time +import gc + +from picographics import PicoGraphics, DISPLAY_PICO_W_EXPLORER, PEN_RGB332 +from picovector import PicoVector, Polygon, RegularPolygon, Rectangle, ANTIALIAS_X4 + + +display = PicoGraphics(DISPLAY_PICO_W_EXPLORER, pen_type=PEN_RGB332) + +vector = PicoVector(display) +vector.set_antialiasing(ANTIALIAS_X4) + +RED = display.create_pen(200, 0, 0) +BLACK = display.create_pen(0, 0, 0) +GREY = display.create_pen(200, 200, 200) +WHITE = display.create_pen(255, 255, 255) + +""" +# Redefine colours for a Blue clock +RED = display.create_pen(200, 0, 0) +BLACK = display.create_pen(135, 159, 169) +GREY = display.create_pen(10, 40, 50) +WHITE = display.create_pen(14, 60, 76) +""" + +WIDTH, HEIGHT = display.get_bounds() + +hub = RegularPolygon(int(WIDTH / 2), int(HEIGHT / 2), 24, 5) + +face = RegularPolygon(int(WIDTH / 2), int(HEIGHT / 2), 48, int(HEIGHT / 2)) + +print(time.localtime()) + +last_second = None + +while True: + t_start = time.ticks_ms() + year, month, day, hour, minute, second, _, _ = time.localtime() + + if last_second == second: + continue + + last_second = second + + display.set_pen(0) + display.clear() + + 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.set_pen(GREY) + + 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) + + 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) + + 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) + vector.draw(hour_hand) + vector.draw(second_hand) + + display.set_pen(BLACK) + + 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) + + 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) + vector.draw(minute_hand) + vector.draw(hour_hand) + + display.set_pen(RED) + vector.translate(second_hand, 0, -5) + vector.draw(second_hand) + vector.draw(hub) + + display.update() + gc.collect() + + t_end = time.ticks_ms() + print(f"Took {t_end - t_start}ms") diff --git a/micropython/examples/picow_explorer/vector_spectrometer.py b/micropython/examples/picow_explorer/vector_spectrometer.py new file mode 100644 index 00000000..e04d8937 --- /dev/null +++ b/micropython/examples/picow_explorer/vector_spectrometer.py @@ -0,0 +1,119 @@ +import math +import time +from pimoroni_i2c import PimoroniI2C +from breakout_as7262 import BreakoutAS7262 +from picographics import PicoGraphics, DISPLAY_PICO_W_EXPLORER, PEN_RGB332 +from picovector import PicoVector, Polygon, RegularPolygon, ANTIALIAS_X4 + +PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} +PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} +i2c = PimoroniI2C(**PINS_PICO_EXPLORER) + +# Set up the AS7262 Spectrometer +as7262 = BreakoutAS7262(i2c) +as7262.set_gain(BreakoutAS7262.X16) +as7262.set_measurement_mode(BreakoutAS7262.CONT_ROYGBR) +as7262.set_illumination_current(BreakoutAS7262.MA12) +as7262.set_indicator_current(BreakoutAS7262.MA4) +as7262.set_leds(True, True) + +# Set up the display +display = PicoGraphics(DISPLAY_PICO_W_EXPLORER, pen_type=PEN_RGB332) +display.set_backlight(0.8) + +# Set up PicoVector +vector = PicoVector(display) +vector.set_antialiasing(ANTIALIAS_X4) + +# Load an Alright Font +result = vector.set_font("/AdvRe.af", 30) + +WIDTH, HEIGHT = display.get_bounds() + +CENTER_X = int(WIDTH / 2) +CENTER_Y = int(HEIGHT / 2) + +RADIUS = 90 +DEBUG = False + +RED = display.create_pen(255, 0, 0) +ORANGE = display.create_pen(255, 128, 0) +YELLOW = display.create_pen(255, 255, 0) +GREEN = display.create_pen(0, 255, 0) +BLUE = display.create_pen(0, 0, 255) +VIOLET = display.create_pen(255, 0, 255) + +BLACK = display.create_pen(0, 0, 0) +GREY = display.create_pen(128, 128, 128) +WHITE = display.create_pen(255, 255, 255) + +LABELS = ["R", "O", "Y", "G", "B", "V"] +COLS = [RED, ORANGE, YELLOW, GREEN, BLUE, VIOLET] + + +# Custom regular_polygon function to give each point its own "radius" +def regular_polygon(o_x, o_y, radius, rotation): + sides = 6 + angle = math.radians(360 / sides) + rotation = math.radians(rotation) + + points = [] + + for side in range(sides): + current_angle = side * angle + rotation + x = math.cos(current_angle) * radius[side] + y = math.sin(current_angle) * radius[side] + points.append((int(x) + o_x, int(y) + o_y)) + + return points + + +lines = RegularPolygon(CENTER_X, CENTER_Y, 6, RADIUS) +label_points = list(RegularPolygon(CENTER_X, CENTER_Y, 6, RADIUS * 0.7, -(360 / 12))) + + +while True: + # Clear to black + display.set_pen(BLACK) + display.clear() + + # Add the title + display.set_pen(WHITE) + vector.text("Spectrograph", 5, -5) + + # Get the spectrometer readings + reading = list(as7262.read()) + + # Print out the readings + if DEBUG: + for i in range(6): + print(f"{LABELS[i]}: {reading[i]:0.2f}", end=" ") + print("") + + # Draw the lines separating each section + display.set_pen(GREY) + for (x, y) in lines: + display.line(CENTER_X, CENTER_Y, int(x), int(y)) + + # Scale readings for display + for i in range(6): + reading[i] = int(reading[i] / 3.0) + reading[i] = min(reading[i], RADIUS) + + # Create a 6 point polygon with each points distance from the center + # scaled by the corresponding reading. + points = regular_polygon(CENTER_X, CENTER_Y, reading, 0) + + # Split the polygon into six triangles, one for each channel + # draw each one, along with its corresponding label + point_a = points[-1] + for i in range(6): + point_b = points[i] + label_x, label_y = label_points[i] + display.set_pen(COLS[i]) + vector.text(LABELS[i], int(label_x) - 5, int(label_y) - 20) + vector.draw(Polygon(point_a, point_b, (CENTER_X, CENTER_Y))) + point_a = point_b + + display.update() + time.sleep(1.0 / 60) diff --git a/micropython/examples/tufty2040/polygons.py b/micropython/examples/tufty2040/polygons.py new file mode 100644 index 00000000..f81a121f --- /dev/null +++ b/micropython/examples/tufty2040/polygons.py @@ -0,0 +1,57 @@ +import time +import math +from picographics import PicoGraphics, DISPLAY_TUFTY_2040, PEN_RGB332 + +display = PicoGraphics(DISPLAY_TUFTY_2040, pen_type=PEN_RGB332) +display.set_backlight(1.0) + +WIDTH, HEIGHT = display.get_bounds() + +BLACK = display.create_pen(0, 0, 0) + + +def scaled_sine(start, finish, speed): + s = math.sin(time.ticks_ms() / speed) + s += 1 # -1 to +1 to 0 to 2 + s /= 2.0 # 0 to 2 to 0 to 1 + s *= finish - start + s += start + return s + + +def regular_polygon(o_x, o_y, sides, radius, rotation): + angle = math.radians(360 / sides) + rotation = math.radians(rotation) + + points = [] + + for side in range(sides): + current_angle = side * angle + rotation + x = math.cos(current_angle) * radius + y = math.sin(current_angle) * radius + points.append((int(x) + o_x, int(y) + o_y)) + + return points + + +while True: + sides = int(scaled_sine(3, 10, 500)) + rotation = time.ticks_ms() / 10 + display.set_pen(BLACK) + display.clear() + + for y in range(HEIGHT): + p = display.create_pen_hsv(y / HEIGHT, 1.0, 1.0) + display.set_pen(p) + display.line(0, y, WIDTH, y) + + display.set_pen(BLACK) + + points_a = regular_polygon(int(WIDTH / 2), int(HEIGHT / 2), sides, 100, rotation) + points_b = regular_polygon(int(WIDTH / 2), int(HEIGHT / 2), sides, 50, -rotation) + + display.pretty_polygon(points_a, points_b) + + display.update() + + time.sleep(1.0 / 60) diff --git a/micropython/modules/micropython-common.cmake b/micropython/modules/micropython-common.cmake index e6f82633..7ad640ea 100644 --- a/micropython/modules/micropython-common.cmake +++ b/micropython/modules/micropython-common.cmake @@ -10,6 +10,7 @@ include(picographics/micropython) # Pico Graphics Extra include(pngdec/micropython) include(jpegdec/micropython) +include(picovector/micropython) include(qrcode/micropython/micropython) # Sensors & Breakouts diff --git a/micropython/modules/micropython-tufty2040.cmake b/micropython/modules/micropython-tufty2040.cmake index 475db55f..11f28bc0 100644 --- a/micropython/modules/micropython-tufty2040.cmake +++ b/micropython/modules/micropython-tufty2040.cmake @@ -14,6 +14,7 @@ include(pimoroni_bus/micropython) # Pico Graphics Essential include(hershey_fonts/micropython) include(bitmap_fonts/micropython) +include(picovector/micropython) include(picographics/micropython) # Pico Graphics Extra diff --git a/micropython/modules/picographics/picographics.cpp b/micropython/modules/picographics/picographics.cpp index 56dde371..e10f78c8 100644 --- a/micropython/modules/picographics/picographics.cpp +++ b/micropython/modules/picographics/picographics.cpp @@ -298,17 +298,17 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size pimoroni::ParallelPins parallel_bus = {10, 11, 12, 13, 14, 2}; // Default for Tufty 2040 parallel pimoroni::I2C *i2c_bus = nullptr; - if (mp_obj_is_type(args[ARG_bus].u_obj, &SPIPins_type)) { + if (mp_obj_is_exact_type(args[ARG_bus].u_obj, &SPIPins_type)) { if(bus_type != BUS_SPI) mp_raise_ValueError("unexpected SPI bus!"); _PimoroniBus_obj_t *bus = (_PimoroniBus_obj_t *)MP_OBJ_TO_PTR(args[ARG_bus].u_obj); spi_bus = *(SPIPins *)(bus->pins); - } else if (mp_obj_is_type(args[ARG_bus].u_obj, &ParallelPins_type)) { + } else if (mp_obj_is_exact_type(args[ARG_bus].u_obj, &ParallelPins_type)) { if(bus_type != BUS_PARALLEL) mp_raise_ValueError("unexpected Parallel bus!"); _PimoroniBus_obj_t *bus = (_PimoroniBus_obj_t *)MP_OBJ_TO_PTR(args[ARG_bus].u_obj); parallel_bus = *(ParallelPins *)(bus->pins); - } else if (mp_obj_is_type(args[ARG_bus].u_obj, &PimoroniI2C_type) || MP_OBJ_IS_TYPE(args[ARG_bus].u_obj, &machine_i2c_type)) { + } else if (mp_obj_is_exact_type(args[ARG_bus].u_obj, &PimoroniI2C_type) || mp_obj_is_exact_type(args[ARG_bus].u_obj, &machine_i2c_type)) { if(bus_type != BUS_I2C) mp_raise_ValueError("unexpected I2C bus!"); self->i2c = PimoroniI2C_from_machine_i2c_or_native(args[ARG_bus].u_obj); i2c_bus = (pimoroni::I2C *)(self->i2c->i2c); @@ -441,7 +441,6 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size if (display != DISPLAY_INKY_FRAME && display != DISPLAY_INKY_FRAME_4 && display != DISPLAY_INKY_PACK && display != DISPLAY_INKY_FRAME_7) { self->display->update(self->graphics); } - return MP_OBJ_FROM_PTR(self); } @@ -807,7 +806,7 @@ mp_obj_t ModPicoGraphics_set_palette(size_t n_args, const mp_obj_t *pos_args, mp // Check if there is only one argument, which might be a list if(n_args == 2) { - if(mp_obj_is_type(pos_args[1], &mp_type_list)) { + if(mp_obj_is_exact_type(pos_args[1], &mp_type_list)) { mp_obj_list_t *points = MP_OBJ_TO_PTR2(pos_args[1], mp_obj_list_t); if(points->len <= 0) mp_raise_ValueError("set_palette(): cannot provide an empty list"); @@ -822,7 +821,7 @@ mp_obj_t ModPicoGraphics_set_palette(size_t n_args, const mp_obj_t *pos_args, mp for(size_t i = 0; i < num_tuples; i++) { mp_obj_t obj = tuples[i]; - if(!mp_obj_is_type(obj, &mp_type_tuple)) mp_raise_ValueError("set_palette(): can't convert object to tuple"); + if(!mp_obj_is_exact_type(obj, &mp_type_tuple)) mp_raise_ValueError("set_palette(): can't convert object to tuple"); mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR2(obj, mp_obj_tuple_t); @@ -1027,7 +1026,7 @@ mp_obj_t ModPicoGraphics_polygon(size_t n_args, const mp_obj_t *pos_args, mp_map // Check if there is only one argument, which might be a list if(n_args == 2) { - if(mp_obj_is_type(pos_args[1], &mp_type_list)) { + if(mp_obj_is_exact_type(pos_args[1], &mp_type_list)) { mp_obj_list_t *points = MP_OBJ_TO_PTR2(pos_args[1], mp_obj_list_t); if(points->len <= 0) mp_raise_ValueError("poly(): cannot provide an empty list"); @@ -1044,7 +1043,7 @@ mp_obj_t ModPicoGraphics_polygon(size_t n_args, const mp_obj_t *pos_args, mp_map std::vector points; for(size_t i = 0; i < num_tuples; i++) { mp_obj_t obj = tuples[i]; - if(!mp_obj_is_type(obj, &mp_type_tuple)) mp_raise_ValueError("poly(): can't convert object to tuple"); + if(!mp_obj_is_exact_type(obj, &mp_type_tuple)) mp_raise_ValueError("poly(): can't convert object to tuple"); mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR2(obj, mp_obj_tuple_t); diff --git a/micropython/modules/picovector/micropython.cmake b/micropython/modules/picovector/micropython.cmake new file mode 100644 index 00000000..69624f20 --- /dev/null +++ b/micropython/modules/picovector/micropython.cmake @@ -0,0 +1,21 @@ +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 +) + +target_include_directories(usermod_picovector INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_vector/ + ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/ +) + +target_compile_definitions(usermod_picovector INTERFACE + MODULE_PICOVECTOR_ENABLED=1 +) + +target_link_libraries(usermod INTERFACE usermod_picovector) diff --git a/micropython/modules/picovector/picovector.c b/micropython/modules/picovector/picovector.c new file mode 100644 index 00000000..eb6dfd1f --- /dev/null +++ b/micropython/modules/picovector/picovector.c @@ -0,0 +1,128 @@ +#include "picovector.h" + +/* Polygon */ + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(POLYGON__del__obj, POLYGON__del__); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(POLYGON_centroid_obj, POLYGON_centroid); + + +STATIC const mp_rom_map_elem_t POLYGON_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&POLYGON__del__obj) }, + { MP_ROM_QSTR(MP_QSTR_centroid), MP_ROM_PTR(&POLYGON_centroid_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(POLYGON_locals_dict, POLYGON_locals_dict_table); + +#ifdef MP_DEFINE_CONST_OBJ_TYPE +MP_DEFINE_CONST_OBJ_TYPE( + POLYGON_type, + MP_QSTR_polygon, + MP_TYPE_FLAG_NONE, + make_new, POLYGON_make_new, + print, POLYGON_print, + iter, POLYGON_getiter, + locals_dict, (mp_obj_dict_t*)&POLYGON_locals_dict +); +MP_DEFINE_CONST_OBJ_TYPE( + REGULAR_POLYGON_type, + MP_QSTR_regular_polygon, + MP_TYPE_FLAG_NONE, + make_new, REGULAR_POLYGON_make_new, + locals_dict, (mp_obj_dict_t*)&POLYGON_locals_dict +); +MP_DEFINE_CONST_OBJ_TYPE( + RECTANGLE_type, + MP_QSTR_rectangle, + MP_TYPE_FLAG_NONE, + make_new, RECTANGLE_make_new, + locals_dict, (mp_obj_dict_t*)&POLYGON_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_3(VECTOR_set_font_obj, VECTOR_set_font); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_size_obj, VECTOR_set_font_size); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_antialiasing_obj, VECTOR_set_antialiasing); + +STATIC MP_DEFINE_CONST_FUN_OBJ_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 const mp_rom_map_elem_t VECTOR_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_set_font), MP_ROM_PTR(&VECTOR_set_font_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_font_size), MP_ROM_PTR(&VECTOR_set_font_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_antialiasing), MP_ROM_PTR(&VECTOR_set_antialiasing_obj) }, + { MP_ROM_QSTR(MP_QSTR_text), MP_ROM_PTR(&VECTOR_text_obj) }, + + { MP_ROM_QSTR(MP_QSTR_draw), MP_ROM_PTR(&VECTOR_draw_obj) }, + { MP_ROM_QSTR(MP_QSTR_rotate), MP_ROM_PTR(&VECTOR_rotate_obj) }, + { MP_ROM_QSTR(MP_QSTR_translate), MP_ROM_PTR(&VECTOR_translate_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(VECTOR_locals_dict, VECTOR_locals_dict_table); + +#ifdef MP_DEFINE_CONST_OBJ_TYPE +MP_DEFINE_CONST_OBJ_TYPE( + VECTOR_type, + MP_QSTR_picovector, + MP_TYPE_FLAG_NONE, + 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 */ + +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_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) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_VECTOR_globals, VECTOR_globals_table); + +const mp_obj_module_t VECTOR_user_cmodule = { + .base = { &mp_type_module }, + .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 diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp new file mode 100644 index 00000000..9654c342 --- /dev/null +++ b/micropython/modules/picovector/picovector.cpp @@ -0,0 +1,459 @@ +#include "micropython/modules/util.hpp" +#include "libraries/pico_graphics/pico_graphics.hpp" +#include "libraries/pico_vector/pico_vector.hpp" + +using namespace pimoroni; + +extern "C" { +#include "picovector.h" +#include "micropython/modules/picographics/picographics.h" +#include "py/stream.h" +#include "py/reader.h" +#include "extmod/vfs.h" + +typedef struct _ModPicoGraphics_obj_t { + mp_obj_base_t base; + PicoGraphics *graphics; + DisplayDriver *display; + void *buffer; +} ModPicoGraphics_obj_t; + +typedef struct _VECTOR_obj_t { + mp_obj_base_t base; + void *mem; + PicoVector *vector; +} _VECTOR_obj_t; + +typedef struct _POLYGON_obj_t { + mp_obj_base_t base; + pretty_poly::contour_t contour; +} _POLYGON_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()); + + //mp_printf(&mp_plat_print, "Opening file %s\n", filename.data()); + + mp_obj_t args[2] = { + fn, + MP_OBJ_NEW_QSTR(MP_QSTR_r), + }; + + // 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 fhandle = mp_vfs_open(MP_ARRAY_SIZE(args), &args[0], (mp_map_t *)&mp_const_empty_map); + + this->state = (void *)fhandle; +} + +pretty_poly::file_io::~file_io() { + mp_stream_close((mp_obj_t)this->state); +} + +size_t pretty_poly::file_io::read(void *buf, size_t len) { + //mp_printf(&mp_plat_print, "Reading %lu bytes\n", len); + mp_obj_t fhandle = this->state; + int error; + return mp_stream_read_exactly(fhandle, buf, len, &error); +} + +size_t pretty_poly::file_io::tell() { + mp_obj_t fhandle = this->state; + 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); + + int error; + mp_uint_t res = stream_p->ioctl(fhandle, MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error); + if (res == MP_STREAM_ERROR) { + mp_raise_OSError(error); + } + + 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; + 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); + + int error; + mp_uint_t res = stream_p->ioctl(fhandle, MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error); + if (res == MP_STREAM_ERROR) { + mp_raise_OSError(error); + } + + 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); + return std::string_view((const char*)str, str_len); + } + 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 }; + 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_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + _POLYGON_obj_t *self = m_new_obj_with_finaliser(_POLYGON_obj_t); + self->base.type = &POLYGON_type; + + int x = args[ARG_x].u_int; + int y = args[ARG_y].u_int; + int w = args[ARG_w].u_int; + int h = args[ARG_h].u_int; + + self->contour.points = m_new(pretty_poly::point_t, 4); + self->contour.count = 4; + + self->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)}; + + 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 }; + 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_radius, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_rotation, 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); + + _POLYGON_obj_t *self = m_new_obj_with_finaliser(_POLYGON_obj_t); + self->base.type = &POLYGON_type; + + 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); + + 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 + }; + } + + 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 = m_new_obj_with_finaliser(_POLYGON_obj_t); + self->base.type = &POLYGON_type; + + size_t num_points = n_args; + const mp_obj_t *points = all_args; + + if(num_points < 3) mp_raise_ValueError("Polygon: At least 3 points required."); + + self->contour.points = m_new(pretty_poly::point_t, num_points); + self->contour.count = num_points; + + 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"); + + 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]), + }; + } + + return self; +} + +mp_obj_t POLYGON_centroid(mp_obj_t self_in) { + _POLYGON_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLYGON_obj_t); + + pretty_poly::point_t sum(0, 0); + + for(auto i = 0u; i < self->contour.count; i++) { + sum += self->contour.points[i]; + } + + sum /= (float)self->contour.count; + + mp_obj_t tuple[2]; + tuple[0] = mp_obj_new_int((int)(sum.x)); + tuple[1] = mp_obj_new_int((int)(sum.y)); + + return mp_obj_new_tuple(2, tuple); +} + +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); + + 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, ")"); +} + +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 + return mp_const_none; +} + +typedef struct _mp_obj_polygon_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_t polygon; + size_t cur; +} mp_obj_polygon_it_t; + +STATIC mp_obj_t py_image_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); + + //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; + + 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)); + + self->cur++; + return mp_obj_new_tuple(2, tuple); +} + +mp_obj_t POLYGON_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { + mp_obj_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->polygon = o_in; + o->cur = 0; + return MP_OBJ_FROM_PTR(o); +} + +/* 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) { + enum { + ARG_picographics + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_picographics, MP_ARG_REQUIRED | MP_ARG_OBJ }, + }; + 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); + + if(!MP_OBJ_IS_TYPE(args[ARG_picographics].u_obj, &ModPicoGraphics_type)) mp_raise_ValueError(MP_ERROR_TEXT("PicoGraphics Object Required")); + + _VECTOR_obj_t *self = m_new_obj(_VECTOR_obj_t); + self->base.type = &VECTOR_type; + ModPicoGraphics_obj_t *graphics = (ModPicoGraphics_obj_t *)MP_OBJ_TO_PTR(args[ARG_picographics].u_obj); + + // 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()); + + self->vector = m_new_class(PicoVector, graphics->graphics, self->mem); + + return self; +} + +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); + bool result = false; + + if (mp_obj_is_str(font)) { + result = self->vector->set_font(mp_obj_to_string_r(font), font_size); + } + else { + + } + return result ? mp_const_true : mp_const_false; +} + +mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size) { + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); + + int font_size = mp_obj_get_int(size); + self->vector->set_font_size(font_size); + return mp_const_none; +} + +mp_obj_t VECTOR_set_antialiasing(mp_obj_t self_in, mp_obj_t aa) { + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); + + self->vector->set_antialiasing((pretty_poly::antialias_t)mp_obj_get_int(aa)); + return mp_const_none; +} + +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 }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_text, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_angle, MP_ARG_OBJ, {.u_obj = mp_const_none} } + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _VECTOR_obj_t); + + mp_obj_t text_obj = args[ARG_text].u_obj; + + if(!mp_obj_is_str_or_bytes(text_obj)) mp_raise_TypeError("text: string required"); + + GET_STR_DATA_LEN(text_obj, str, str_len); + + const std::string_view t((const char*)str, str_len); + + int x = args[ARG_x].u_int; + int y = args[ARG_y].u_int; + + 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)); + } + + return mp_const_none; +} + +mp_obj_t VECTOR_rotate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_polygon, ARG_angle, ARG_origin_x, ARG_origin_y }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_polygon, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_angle, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_origin_x, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_origin_y, MP_ARG_INT, {.u_int = 0} } + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _VECTOR_obj_t); + + if(!MP_OBJ_IS_TYPE(args[ARG_polygon].u_obj, &POLYGON_type)) mp_raise_TypeError("rotate: polygon required"); + + _POLYGON_obj_t *poly = MP_OBJ_TO_PTR2(args[ARG_polygon].u_obj, _POLYGON_obj_t); + + Point origin = Point(args[ARG_origin_x].u_int, args[ARG_origin_y].u_int); + + float angle = mp_obj_get_float(args[ARG_angle].u_obj); + + self->vector->rotate(poly->contour, origin, angle); + + return mp_const_none; +} + +mp_obj_t VECTOR_translate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_polygon, ARG_x, ARG_y }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_polygon, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_x, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_y, MP_ARG_INT, {.u_int = 0} } + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _VECTOR_obj_t); + + if(!MP_OBJ_IS_TYPE(args[ARG_polygon].u_obj, &POLYGON_type)) mp_raise_TypeError("rotate: polygon required"); + + _POLYGON_obj_t *poly = MP_OBJ_TO_PTR2(args[ARG_polygon].u_obj, _POLYGON_obj_t); + + Point translate = Point(args[ARG_x].u_int, args[ARG_y].u_int); + + self->vector->translate(poly->contour, translate); + + return mp_const_none; +} + +mp_obj_t VECTOR_draw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + + size_t num_polygons = n_args - 1; + const mp_obj_t *polygons = pos_args + 1; + + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(pos_args[0], _VECTOR_obj_t); + + std::vector> contours; + + for(auto i = 0u; i < num_polygons; i++) { + mp_obj_t poly_obj = polygons[i]; + + if(!MP_OBJ_IS_TYPE(poly_obj, &POLYGON_type)) mp_raise_TypeError("draw: Polygon required."); + + _POLYGON_obj_t *poly = MP_OBJ_TO_PTR2(poly_obj, _POLYGON_obj_t); + contours.emplace_back(poly->contour.points, poly->contour.count); + } + + self->vector->polygon(contours); + + return mp_const_none; +} + +} diff --git a/micropython/modules/picovector/picovector.h b/micropython/modules/picovector/picovector.h new file mode 100644 index 00000000..a4158e88 --- /dev/null +++ b/micropython/modules/picovector/picovector.h @@ -0,0 +1,27 @@ +#include "py/runtime.h" +#include "py/objstr.h" + +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 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 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_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf); + +extern mp_obj_t POLYGON__del__(mp_obj_t self_in); + +extern mp_obj_t VECTOR_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); + +extern mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size); +extern mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size); +extern mp_obj_t VECTOR_set_antialiasing(mp_obj_t self_in, mp_obj_t aa); + +extern mp_obj_t VECTOR_draw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +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 diff --git a/micropython/modules/pngdec/pngdec.cpp b/micropython/modules/pngdec/pngdec.cpp index 90a34f3d..9e69f596 100644 --- a/micropython/modules/pngdec/pngdec.cpp +++ b/micropython/modules/pngdec/pngdec.cpp @@ -96,7 +96,7 @@ int32_t pngdec_seek_callback(PNGFILE *png, int32_t p) { void pngdec_open_helper(_PNG_obj_t *self) { int result = -1; - if(mp_obj_is_str_or_bytes(self->file)){ + if(mp_obj_is_str(self->file)){ GET_STR_DATA_LEN(self->file, str, str_len); result = self->png->open( @@ -188,10 +188,16 @@ MICROPY_EVENT_POLL_HOOK uint8_t i = 0; if(pDraw->iBpp == 8) { i = *pixel++; - } else { - i = pixel[x / 2]; + } else if (pDraw->iBpp == 4) { + i = *pixel; i >>= (x & 0b1) ? 0 : 4; i &= 0xf; + if (x & 1) pixel++; + } else { + i = *pixel; + i >>= 6 - ((x & 0b11) << 1); + i &= 0x3; + if ((x & 0b11) == 0b11) pixel++; } if(x < target->source.x || x >= target->source.x + target->source.w) continue; // grab the colour from the palette @@ -264,7 +270,7 @@ mp_obj_t _PNG_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, co self->base.type = &PNG_type; self->png = m_new_class(PNG); - mp_printf(&mp_plat_print, "PNG RAM %fK\n", sizeof(PNG) / 1024.0f); + //mp_printf(&mp_plat_print, "PNG RAM %fK\n", sizeof(PNG) / 1024.0f); ModPicoGraphics_obj_t *graphics = (ModPicoGraphics_obj_t *)MP_OBJ_TO_PTR(args[ARG_picographics].u_obj);