diff --git a/libraries/pico_graphics/pico_graphics.cmake b/libraries/pico_graphics/pico_graphics.cmake index e5320dd2..c69eb7d7 100644 --- a/libraries/pico_graphics/pico_graphics.cmake +++ b/libraries/pico_graphics/pico_graphics.cmake @@ -5,4 +5,4 @@ add_library(pico_graphics target_include_directories(pico_graphics INTERFACE ${CMAKE_CURRENT_LIST_DIR}) -target_link_libraries(pico_graphics bitmap_fonts pico_stdlib) \ No newline at end of file +target_link_libraries(pico_graphics bitmap_fonts hershey_fonts pico_stdlib) \ No newline at end of file diff --git a/libraries/pico_graphics/pico_graphics.cpp b/libraries/pico_graphics/pico_graphics.cpp index ed10231c..263ffed7 100644 --- a/libraries/pico_graphics/pico_graphics.cpp +++ b/libraries/pico_graphics/pico_graphics.cpp @@ -4,10 +4,10 @@ namespace pimoroni { void PicoGraphics::set_pen(uint c) {}; void PicoGraphics::set_pen(uint8_t r, uint8_t g, uint8_t b) {}; - void PicoGraphics::update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) {}; - void PicoGraphics::reset_pen(uint8_t i) {}; + int PicoGraphics::update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) {return -1;}; + int PicoGraphics::reset_pen(uint8_t i) {return -1;}; int PicoGraphics::create_pen(uint8_t r, uint8_t g, uint8_t b) {return -1;}; - void PicoGraphics::set_pixel(void *frame_buffer, uint x, uint y, uint stride) {}; + void PicoGraphics::set_pixel(const Point &p) {}; void PicoGraphics::palette_lookup(void *frame_buffer, void *result, uint offset, uint length) {}; void PicoGraphics::set_dimensions(int width, int height) { @@ -27,16 +27,27 @@ namespace pimoroni { } void PicoGraphics::set_font(const bitmap::font_t *font){ - this->font = font; + this->bitmap_font = font; + this->hershey_font = nullptr; } - void PicoGraphics::set_font(std::string font){ - if (font == "bitmap6") { - this->font = &font6; - } else if (font == "bitmap8") { - this->font = &font8; - } else if (font == "bitmap14_outline") { - this->font = &font14_outline; + void PicoGraphics::set_font(const hershey::font_t *font){ + this->bitmap_font = nullptr; + this->hershey_font = font; + } + + void PicoGraphics::set_font(std::string name){ + if (name == "bitmap6") { + set_font(&font6); + } else if (name == "bitmap8") { + set_font(&font8); + } else if (name == "bitmap14_outline") { + set_font(&font14_outline); + } else { + // check that font exists and assign it + if(hershey::fonts.find(name) != hershey::fonts.end()) { + set_font(hershey::fonts[name]); + } } } @@ -54,7 +65,7 @@ namespace pimoroni { void PicoGraphics::pixel(const Point &p) { if(!clip.contains(p)) return; - set_pixel(frame_buffer, p.x, p.y, bounds.w); + set_pixel(p); } void PicoGraphics::pixel_span(const Point &p, int32_t l) { @@ -69,7 +80,7 @@ namespace pimoroni { Point dest(clipped.x, clipped.y); while(l--) { - set_pixel(frame_buffer, dest.x, dest.y, bounds.w); + set_pixel(dest); dest.x++; } } @@ -117,20 +128,42 @@ namespace pimoroni { } } - void PicoGraphics::character(const char c, const Point &p, uint8_t scale) { - bitmap::character(font, [this](int32_t x, int32_t y, int32_t w, int32_t h){ - rectangle(Rect(x, y, w, h)); - }, c, p.x, p.y, scale); + void PicoGraphics::character(const char c, const Point &p, float s, float a) { + if (bitmap_font) { + bitmap::character(bitmap_font, [this](int32_t x, int32_t y, int32_t w, int32_t h) { + rectangle(Rect(x, y, w, h)); + }, c, p.x, p.y, std::max(1.0f, s)); + return; + } + + if (hershey_font) { + hershey::glyph(hershey_font, [this](int32_t x1, int32_t y1, int32_t x2, int32_t y2) { + line(Point(x1, y1), Point(x2, y2)); + }, c, p.x, p.y, s, a); + return; + } } - void PicoGraphics::text(const std::string &t, const Point &p, int32_t wrap, uint8_t scale) { - bitmap::text(font, [this](int32_t x, int32_t y, int32_t w, int32_t h){ - rectangle(Rect(x, y, w, h)); - }, t, p.x, p.y, wrap, scale); + void PicoGraphics::text(const std::string &t, const Point &p, int32_t wrap, float s, float a, uint8_t letter_spacing) { + if (bitmap_font) { + bitmap::text(bitmap_font, [this](int32_t x, int32_t y, int32_t w, int32_t h) { + rectangle(Rect(x, y, w, h)); + }, t, p.x, p.y, wrap, std::max(1.0f, s), letter_spacing); + return; + } + + if (hershey_font) { + hershey::text(hershey_font, [this](int32_t x1, int32_t y1, int32_t x2, int32_t y2) { + line(Point(x1, y1), Point(x2, y2)); + }, t, p.x, p.y, s, a); + return; + } } - int32_t PicoGraphics::measure_text(const std::string &t, uint8_t scale) { - return bitmap::measure_text(font, t, scale); + int32_t PicoGraphics::measure_text(const std::string &t, float s, uint8_t letter_spacing) { + if (bitmap_font) return bitmap::measure_text(bitmap_font, t, std::max(1.0f, s), letter_spacing); + if (hershey_font) return hershey::measure_text(hershey_font, t, s); + return 0; } int32_t orient2d(Point p1, Point p2, Point p3) { @@ -186,7 +219,7 @@ namespace pimoroni { Point dest = Point(triangle_bounds.x, triangle_bounds.y + y); for (int32_t x = 0; x < triangle_bounds.w; x++) { if ((w0 | w1 | w2) >= 0) { - set_pixel(frame_buffer, dest.x, dest.y, bounds.w); + set_pixel(dest); } dest.x++; @@ -251,24 +284,29 @@ namespace pimoroni { void PicoGraphics::line(Point p1, Point p2) { // fast horizontal line if(p1.y == p2.y) { - int32_t start = std::max(clip.x, std::min(p1.x, p2.x)); - int32_t end = std::min(clip.x + clip.w, std::max(p1.x, p2.x)); + p1 = p1.clamp(clip); + p2 = p2.clamp(clip); + int32_t start = std::min(p1.x, p2.x); + int32_t end = std::max(p1.x, p2.x); pixel_span(Point(start, p1.y), end - start); return; } // fast vertical line if(p1.x == p2.x) { - int32_t start = std::max(clip.y, std::min(p1.y, p2.y)); - int32_t length = std::min(clip.y + clip.h, std::max(p1.y, p2.y)) - start; + p1 = p1.clamp(clip); + p2 = p2.clamp(clip); + int32_t start = std::min(p1.y, p2.y); + int32_t length = std::max(p1.y, p2.y) - start; Point dest(p1.x, start); while(length--) { - set_pixel(frame_buffer, dest.x, dest.y, bounds.w); + set_pixel(dest); dest.y++; } return; } + // general purpose line // lines are either "shallow" or "steep" based on whether the x delta // is greater than the y delta @@ -283,7 +321,8 @@ namespace pimoroni { int32_t x = p1.x; int32_t y = p1.y << 16; while(s--) { - set_pixel(frame_buffer, x, y >> 16, bounds.w); + Point p(x, y >> 16); + if(clip.contains(p)) set_pixel(p); y += sy; x += sx; } @@ -295,7 +334,8 @@ namespace pimoroni { int32_t y = p1.y; int32_t x = p1.x << 16; while(s--) { - set_pixel(frame_buffer, x >> 16, y, bounds.w); + Point p(x >> 16, y); + if(clip.contains(p)) set_pixel(p); y += sy; x += sx; } diff --git a/libraries/pico_graphics/pico_graphics.hpp b/libraries/pico_graphics/pico_graphics.hpp index 83c413ae..b7246aa2 100644 --- a/libraries/pico_graphics/pico_graphics.hpp +++ b/libraries/pico_graphics/pico_graphics.hpp @@ -4,9 +4,13 @@ #include #include #include + +#include "libraries/hershey_fonts/hershey_fonts.hpp" +#include "libraries/bitmap_fonts/bitmap_fonts.hpp" #include "libraries/bitmap_fonts/font6_data.hpp" #include "libraries/bitmap_fonts/font8_data.hpp" #include "libraries/bitmap_fonts/font14_outline_data.hpp" + #include "common/pimoroni_common.hpp" // A tiny graphics library for our Pico products @@ -72,7 +76,8 @@ namespace pimoroni { Rect bounds; Rect clip; - const bitmap::font_t *font; + const bitmap::font_t *bitmap_font; + const hershey::font_t *hershey_font; static constexpr RGB332 rgb_to_rgb332(uint8_t r, uint8_t g, uint8_t b) { return (r & 0b11100000) | ((g & 0b11100000) >> 3) | ((b & 0b11000000) >> 6); @@ -100,13 +105,14 @@ namespace pimoroni { virtual void set_pen(uint c); virtual void set_pen(uint8_t r, uint8_t g, uint8_t b); - virtual void update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b); - virtual void reset_pen(uint8_t i); + virtual int update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b); + virtual int reset_pen(uint8_t i); virtual int create_pen(uint8_t r, uint8_t g, uint8_t b); - virtual void set_pixel(void *frame_buffer, uint x, uint y, uint stride); + virtual void set_pixel(const Point &p); virtual void palette_lookup(void *frame_buffer, void *result, uint offset, uint length); void set_font(const bitmap::font_t *font); + void set_font(const hershey::font_t *font); void set_font(std::string font); void set_dimensions(int width, int height); @@ -123,9 +129,9 @@ namespace pimoroni { void pixel_span(const Point &p, int32_t l); void rectangle(const Rect &r); void circle(const Point &p, int32_t r); - void character(const char c, const Point &p, uint8_t scale = 2); - void text(const std::string &t, const Point &p, int32_t wrap, uint8_t scale = 2); - int32_t measure_text(const std::string &t, uint8_t scale = 2); + void character(const char c, const Point &p, float s = 2.0f, float a = 0.0f); + void text(const std::string &t, const Point &p, int32_t wrap, float s = 2.0f, float a = 0.0f, uint8_t letter_spacing = 1); + int32_t measure_text(const std::string &t, float s = 2.0f, uint8_t letter_spacing = 1); void polygon(const std::vector &points); void triangle(Point p1, Point p2, Point p3); void line(Point p1, Point p2); @@ -163,26 +169,28 @@ namespace pimoroni { void set_pen(uint8_t r, uint8_t g, uint8_t b) override { // TODO look up closest palette colour, or just NOOP? } - void update_pen(uint8_t i, 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 { i &= 0xf; palette[i].color = rgb_to_rgb565(r, g, b); palette[i].used = true; + return i; } - void reset_pen(uint8_t i) override { + int reset_pen(uint8_t i) override { i &= 0xf; palette[i].color = default_palette[i]; + return i; } - void set_pixel(void *frame_buffer, uint x, uint y, uint stride) override { + void set_pixel(const Point &p) override { // pointer to byte in framebuffer that contains this pixel uint8_t *buf = (uint8_t *)frame_buffer; - uint8_t *p = &buf[(x / 2) + (y * stride / 2)]; + uint8_t *f = &buf[(p.x / 2) + (p.y * bounds.w / 2)]; - uint8_t o = (~x & 0b1) * 4; // bit offset within byte - uint8_t m = ~(0b1111 << o); // bit mask for byte - uint8_t b = color << o; // bit value shifted to position + uint8_t o = (~p.x & 0b1) * 4; // bit offset within byte + uint8_t m = ~(0b1111 << o); // bit mask for byte + uint8_t b = color << o; // bit value shifted to position - *p &= m; // clear bits - *p |= b; // set value + *f &= m; // clear bits + *f |= b; // set value } void palette_lookup(void *frame_buffer, void *result, uint offset, uint length) override { uint8_t *src = (uint8_t *)frame_buffer; @@ -219,15 +227,17 @@ namespace pimoroni { void set_pen(uint8_t r, uint8_t g, uint8_t b) override { // TODO look up closest palette colour, or just NOOP? } - void update_pen(uint8_t i, 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 { i &= 0xff; palette[i].color = rgb_to_rgb565(r, g, b); palette[i].used = true; + return i; } - void reset_pen(uint8_t i) override { + int reset_pen(uint8_t i) override { i &= 0xff; palette[i].color = 0; palette[i].used = false; + return i; } int create_pen(uint8_t r, uint8_t g, uint8_t b) override { // Create a colour and place it in the palette if there's space @@ -241,9 +251,9 @@ namespace pimoroni { } return -1; } - void set_pixel(void *frame_buffer, uint x, uint y, uint stride) override { + void set_pixel(const Point &p) override { uint8_t *buf = (uint8_t *)frame_buffer; - buf[y * stride + x] = color; + buf[p.y * bounds.w + p.x] = color; } void palette_lookup(void *frame_buffer, void *result, uint offset, uint length) override { uint8_t *src = (uint8_t *)frame_buffer; @@ -281,9 +291,9 @@ namespace pimoroni { int create_pen(uint8_t r, uint8_t g, uint8_t b) override { return rgb_to_rgb332(r, g, b); } - void set_pixel(void *frame_buffer, uint x, uint y, uint stride) override { + void set_pixel(const Point &p) override { uint8_t *buf = (uint8_t *)frame_buffer; - buf[y * stride + x] = color; + buf[p.y * bounds.w + p.x] = color; } void palette_lookup(void *frame_buffer, void *result, uint offset, uint length) override { uint8_t *src = (uint8_t *)frame_buffer; @@ -316,9 +326,9 @@ namespace pimoroni { int create_pen(uint8_t r, uint8_t g, uint8_t b) override { return rgb_to_rgb565(r, g, b); } - void set_pixel(void *frame_buffer, uint x, uint y, uint stride) override { + void set_pixel(const Point &p) override { uint16_t *buf = (uint16_t *)frame_buffer; - buf[y * stride + x] = color; + buf[p.y * bounds.w + p.x] = color; } static size_t buffer_size(uint w, uint h) { return w * h * sizeof(RGB565); diff --git a/micropython/modules/micropython-common.cmake b/micropython/modules/micropython-common.cmake index 949a3133..29ec616d 100644 --- a/micropython/modules/micropython-common.cmake +++ b/micropython/modules/micropython-common.cmake @@ -29,6 +29,7 @@ include(pico_unicorn/micropython) include(pico_wireless/micropython) include(pico_explorer/micropython) +include(hershey_fonts/micropython) include(bitmap_fonts/micropython) include(plasma/micropython) diff --git a/micropython/modules/picographics/picographics.cpp b/micropython/modules/picographics/picographics.cpp index f1c3e2f0..46c7eef3 100644 --- a/micropython/modules/picographics/picographics.cpp +++ b/micropython/modules/picographics/picographics.cpp @@ -443,14 +443,16 @@ mp_obj_t ModPicoGraphics_character(size_t n_args, const mp_obj_t *pos_args, mp_m } mp_obj_t ModPicoGraphics_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_wrap, ARG_scale }; + enum { ARG_self, ARG_text, ARG_x, ARG_y, ARG_wrap, ARG_scale, ARG_angle, ARG_spacing }; 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_x1, MP_ARG_REQUIRED | MP_ARG_INT }, { MP_QSTR_y1, MP_ARG_REQUIRED | MP_ARG_INT }, { MP_QSTR_wordwrap, MP_ARG_REQUIRED | MP_ARG_INT }, - { MP_QSTR_scale, MP_ARG_INT, {.u_int = 2} }, + { MP_QSTR_scale, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_angle, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_spacing, MP_ARG_INT, {.u_int = 1} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -469,19 +471,22 @@ mp_obj_t ModPicoGraphics_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t int x = args[ARG_x].u_int; int y = args[ARG_y].u_int; int wrap = args[ARG_wrap].u_int; - int scale = args[ARG_scale].u_int; + float scale = args[ARG_scale].u_obj == mp_const_none ? 2.0f : mp_obj_get_float(args[ARG_scale].u_obj); + int angle = args[ARG_angle].u_int; + int letter_spacing = args[ARG_spacing].u_int; - self->graphics->text(t, Point(x, y), wrap, scale); + self->graphics->text(t, Point(x, y), wrap, scale, angle, letter_spacing); return mp_const_none; } mp_obj_t ModPicoGraphics_measure_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_self, ARG_text, ARG_scale }; + enum { ARG_self, ARG_text, ARG_scale, ARG_spacing }; 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_scale, MP_ARG_INT, {.u_int = 2} }, + { MP_QSTR_scale, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_spacing, MP_ARG_INT, {.u_int = 1} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -497,9 +502,10 @@ mp_obj_t ModPicoGraphics_measure_text(size_t n_args, const mp_obj_t *pos_args, m std::string t((const char*)str); - int scale = args[ARG_scale].u_int; + float scale = args[ARG_scale].u_obj == mp_const_none ? 2.0f : mp_obj_get_float(args[ARG_scale].u_obj); + int letter_spacing = args[ARG_spacing].u_int; - int width = self->graphics->measure_text(t, scale); + int width = self->graphics->measure_text(t, scale, letter_spacing); return mp_obj_new_int(width); }