From c4decc500358b17670afa4d862d095336084a894 Mon Sep 17 00:00:00 2001 From: Philip Howard Date: Thu, 9 Feb 2023 16:13:37 +0000 Subject: [PATCH] PicoGraphics: HSV Pen (#665) Co-authored-by: Gee Bartlett <122281230+ageeandakeyboard@users.noreply.github.com> --- .../interstate75/interstate75_rainbow.cmake | 1 + .../interstate75/interstate75_rainbow.cpp | 42 ++++++------------ libraries/interstate75/interstate75.cmake | 2 +- libraries/pico_graphics/pico_graphics.cpp | 19 ++++++++ libraries/pico_graphics/pico_graphics.hpp | 15 ++++++- .../pico_graphics_pen_rgb565.cpp | 9 +++- .../pico_graphics_pen_rgb888.cpp | 7 +++ micropython/examples/interstate75/rainbow.py | 43 +++---------------- .../modules/picographics/picographics.c | 2 + .../modules/picographics/picographics.cpp | 15 +++++++ .../modules/picographics/picographics.h | 2 + 11 files changed, 88 insertions(+), 69 deletions(-) diff --git a/examples/interstate75/interstate75_rainbow.cmake b/examples/interstate75/interstate75_rainbow.cmake index ce76c3b9..36165c6e 100644 --- a/examples/interstate75/interstate75_rainbow.cmake +++ b/examples/interstate75/interstate75_rainbow.cmake @@ -11,6 +11,7 @@ target_link_libraries(${OUTPUT_NAME} pico_multicore hardware_vreg hardware_pwm + pico_graphics hub75 interstate75 ) diff --git a/examples/interstate75/interstate75_rainbow.cpp b/examples/interstate75/interstate75_rainbow.cpp index 49b60777..5e012562 100644 --- a/examples/interstate75/interstate75_rainbow.cpp +++ b/examples/interstate75/interstate75_rainbow.cpp @@ -25,33 +25,12 @@ Hub75 hub75(32, 32, nullptr, PANEL_GENERIC, false); //Hub75 hub75(128, 32, nullptr, PANEL_GENERIC, false); //Works with our 64x64 panel https://shop.pimoroni.com/products/rgb-led-matrix-panel?variant=3029531983882 -//Hub75 hub75(64, 64, nullptr, PANEL_GENERIC, false); +//Hub75 hub75(64, 64, nullptr, PANEL_GENERIC, true); //or using 2 of these panels //Hub75 hub75(128, 64, nullptr, PANEL_GENERIC, false); PicoGraphics_PenRGB888 graphics(hub75.width, hub75.height, nullptr); -// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel -// Outputs are rgb in the range 0-255 for each channel -void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) { - float i = floor(h * 6.0f); - float f = h * 6.0f - i; - v *= 255.0f; - uint8_t p = v * (1.0f - s); - uint8_t q = v * (1.0f - f * s); - uint8_t t = v * (1.0f - (1.0f - f) * s); - - switch (int(i) % 6) { - case 0: r = v; g = t; b = p; break; - case 1: r = q; g = v; b = p; break; - case 2: r = p; g = v; b = t; break; - case 3: r = p; g = q; b = v; break; - case 4: r = t; g = p; b = v; break; - case 5: r = v; g = p; b = q; break; - } -} - - // Interrupt callback required function void __isr dma_complete() { hub75.dma_complete(); @@ -63,7 +42,13 @@ int main() { uint8_t hue_map[hub75.width][3]; for(uint i = 0; i < hub75.width; i++) { - from_hsv(i / (float) hub75.width, 1.0f, 1.0f, hue_map[i][0], hue_map[i][1], hue_map[i][2]); + uint8_t r=0; + uint8_t g=0; + uint8_t b=0; + graphics.from_hsv(i / (float) hub75.width, 1.0f, 0.7f, r, g, b); + hue_map[i][0] = r; + hue_map[i][1] = g; + hue_map[i][2] = b; } hub75.start(dma_complete); @@ -83,12 +68,12 @@ int main() { } if(button_a.raw()) { - speed += 0.05f; + speed += 0.1f; speed = speed >= 10.0f ? 10.0f : speed; animate = true; } if(button_b.raw()) { - speed -= 0.05f; + speed -= 0.1f; speed = speed <= 0.0f ? 0.0f : speed; animate = true; } @@ -103,7 +88,7 @@ int main() { graphics.set_pen(r, g, b); graphics.pixel(Point(x, y)); - } + } } hub75.update(&graphics); @@ -111,10 +96,9 @@ int main() { led_h += 0.01; - sleep_ms(20); + //sleep_ms(5); } - printf("done\n"); - return 0; } + diff --git a/libraries/interstate75/interstate75.cmake b/libraries/interstate75/interstate75.cmake index 34ca6b2f..959e3c2f 100644 --- a/libraries/interstate75/interstate75.cmake +++ b/libraries/interstate75/interstate75.cmake @@ -4,4 +4,4 @@ add_library(${LIB_NAME} INTERFACE) target_include_directories(${LIB_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) # Pull in pico libraries that we need -target_link_libraries(${LIB_NAME} INTERFACE pico_stdlib hub75 button rgbled hardware_pwm) \ No newline at end of file +target_link_libraries(${LIB_NAME} INTERFACE pico_stdlib hub75 button rgbled hardware_pwm pico_graphics) \ No newline at end of file diff --git a/libraries/pico_graphics/pico_graphics.cpp b/libraries/pico_graphics/pico_graphics.cpp index 3e481f6d..1ace1d3f 100644 --- a/libraries/pico_graphics/pico_graphics.cpp +++ b/libraries/pico_graphics/pico_graphics.cpp @@ -4,9 +4,28 @@ namespace pimoroni { const uint8_t dither16_pattern[16] = {0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5}; + void PicoGraphics::from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) { + float i = floor(h * 6.0f); + float f = h * 6.0f - i; + v *= 255.0f; + uint8_t p = v * (1.0f - s); + uint8_t q = v * (1.0f - f * s); + uint8_t t = v * (1.0f - (1.0f - f) * s); + + switch (int(i) % 6) { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + } + } + 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;}; + int PicoGraphics::create_pen_hsv(float h, float s, float v){return -1;}; 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) {}; diff --git a/libraries/pico_graphics/pico_graphics.hpp b/libraries/pico_graphics/pico_graphics.hpp index 98aed194..d9f421ca 100644 --- a/libraries/pico_graphics/pico_graphics.hpp +++ b/libraries/pico_graphics/pico_graphics.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "libraries/hershey_fonts/hershey_fonts.hpp" #include "libraries/bitmap_fonts/bitmap_fonts.hpp" @@ -25,6 +26,8 @@ namespace pimoroni { typedef uint8_t RGB332; typedef uint16_t RGB565; typedef uint32_t RGB888; + + struct RGB { int16_t r, g, b; @@ -90,6 +93,8 @@ namespace pimoroni { } }; + + typedef int Pen; struct Rect; @@ -171,6 +176,9 @@ namespace pimoroni { Rect bounds; Rect clip; + + + typedef std::function conversion_callback_func; typedef std::function next_pixel_func; //typedef std::function scanline_interrupt_func; @@ -184,6 +192,7 @@ namespace pimoroni { return RGB(r, g, b).to_rgb332(); } + static constexpr RGB565 rgb332_to_rgb565(RGB332 c) { uint16_t p = ((c & 0b11100000) << 8) | ((c & 0b00011100) << 6) | @@ -221,6 +230,7 @@ namespace pimoroni { virtual void set_pixel_span(const Point &p, uint l) = 0; virtual int create_pen(uint8_t r, uint8_t g, uint8_t b); + virtual int create_pen_hsv(float h, float s, float v); virtual int update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b); virtual int reset_pen(uint8_t i); virtual void set_pixel_dither(const Point &p, const RGB &c); @@ -253,6 +263,7 @@ namespace pimoroni { void polygon(const std::vector &points); void triangle(Point p1, Point p2, Point p3); void line(Point p1, Point p2); + void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b); protected: void frame_convert_rgb565(conversion_callback_func callback, next_pixel_func get_next_pixel); @@ -400,7 +411,6 @@ namespace pimoroni { 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; - void set_pixel(const Point &p) override; void set_pixel_span(const Point &p, uint l) override; void set_pixel_dither(const Point &p, const RGB &c) override; @@ -422,6 +432,7 @@ namespace pimoroni { 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; + int create_pen_hsv(float h, float s, float v) override; void set_pixel(const Point &p) override; void set_pixel_span(const Point &p, uint l) override; static size_t buffer_size(uint w, uint h) { @@ -429,7 +440,6 @@ namespace pimoroni { } }; - class PicoGraphics_PenRGB888 : public PicoGraphics { public: RGB src_color; @@ -438,6 +448,7 @@ namespace pimoroni { 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; + int create_pen_hsv(float h, float s, float v) override; void set_pixel(const Point &p) override; void set_pixel_span(const Point &p, uint l) override; static size_t buffer_size(uint w, uint h) { diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp index 37520703..5d1cf57a 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp @@ -13,11 +13,18 @@ namespace pimoroni { } void PicoGraphics_PenRGB565::set_pen(uint8_t r, uint8_t g, uint8_t b) { src_color = {r, g, b}; - color = src_color.to_rgb565(); + color = src_color.to_rgb565(); } int PicoGraphics_PenRGB565::create_pen(uint8_t r, uint8_t g, uint8_t b) { return RGB(r, g, b).to_rgb565(); } + int PicoGraphics_PenRGB565::create_pen_hsv(float h, float s, float v) { + uint8_t r; + uint8_t g; + uint8_t b; + from_hsv(h, s, v, r, g, b); + return RGB(r, g, b).to_rgb565(); + } void PicoGraphics_PenRGB565::set_pixel(const Point &p) { uint16_t *buf = (uint16_t *)frame_buffer; buf[p.y * bounds.w + p.x] = color; diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp index 89713f93..9758f49f 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp @@ -18,6 +18,13 @@ namespace pimoroni { int PicoGraphics_PenRGB888::create_pen(uint8_t r, uint8_t g, uint8_t b) { return RGB(r, g, b).to_rgb888(); } + int PicoGraphics_PenRGB888::create_pen_hsv(float h, float s, float v) { + uint8_t r; + uint8_t g; + uint8_t b; + from_hsv(h, s, v, r, g, b); + return RGB(r, g, b).to_rgb888(); + } void PicoGraphics_PenRGB888::set_pixel(const Point &p) { uint32_t *buf = (uint32_t *)frame_buffer; buf[p.y * bounds.w + p.x] = color; diff --git a/micropython/examples/interstate75/rainbow.py b/micropython/examples/interstate75/rainbow.py index de812587..0d8aaaf5 100644 --- a/micropython/examples/interstate75/rainbow.py +++ b/micropython/examples/interstate75/rainbow.py @@ -1,60 +1,30 @@ import time -import math import interstate75 -i75 = interstate75.Interstate75(display=interstate75.DISPLAY_INTERSTATE75_32X32) +i75 = interstate75.Interstate75(display=interstate75.DISPLAY_INTERSTATE75_64X64, stb_invert=True) graphics = i75.display width = i75.width height = i75.height - -@micropython.native # noqa: F821 -def from_hsv(h, s, v): - i = math.floor(h * 6.0) - f = h * 6.0 - i - v *= 255.0 - p = v * (1.0 - s) - q = v * (1.0 - f * s) - t = v * (1.0 - (1.0 - f) * s) - - i = int(i) % 6 - if i == 0: - return int(v), int(t), int(p) - if i == 1: - return int(q), int(v), int(p) - if i == 2: - return int(p), int(v), int(t) - if i == 3: - return int(p), int(q), int(v) - if i == 4: - return int(t), int(p), int(v) - if i == 5: - return int(v), int(p), int(q) +devs = 1.0 / height @micropython.native # noqa: F821 -def draw(): - global hue_offset, phase - phase_percent = phase / 15 +def draw(offset): for x in range(width): - colour = hue_map[int((x + (hue_offset * width)) % width)] + graphics.set_pen(graphics.create_pen_hsv(devs * x + offset, 1.0, 0.5)) for y in range(height): - v = ((math.sin((x + y) / stripe_width + phase_percent) + 1.5) / 2.5) - graphics.set_pen(graphics.create_pen(int(colour[0] * v), int(colour[1] * v), int(colour[2] * v))) graphics.pixel(x, y) i75.update(graphics) -hue_map = [from_hsv(x / width, 1.0, 0.5) for x in range(width)] -hue_offset = 0.0 - animate = True stripe_width = 3.0 speed = 5.0 - +offset = 0.0 phase = 0 while True: @@ -63,6 +33,7 @@ while True: phase += speed start = time.ticks_ms() - draw() + offset += 0.05 + draw(offset) print("total took: {} ms".format(time.ticks_ms() - start)) diff --git a/micropython/modules/picographics/picographics.c b/micropython/modules/picographics/picographics.c index edd93098..bf01cdc5 100644 --- a/micropython/modules/picographics/picographics.c +++ b/micropython/modules/picographics/picographics.c @@ -21,6 +21,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(ModPicoGraphics_set_palette_obj, 2, ModPicoGraphics_s // Pen MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_pen_obj, ModPicoGraphics_set_pen); MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_create_pen_obj, 4, 4, ModPicoGraphics_create_pen); +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_create_pen_hsv_obj, 4, 4, ModPicoGraphics_create_pen_hsv); // Primitives MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_set_clip_obj, 5, 5, ModPicoGraphics_set_clip); @@ -77,6 +78,7 @@ STATIC const mp_rom_map_elem_t ModPicoGraphics_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_sprite), MP_ROM_PTR(&ModPicoGraphics_sprite_obj) }, { MP_ROM_QSTR(MP_QSTR_create_pen), MP_ROM_PTR(&ModPicoGraphics_create_pen_obj) }, + { MP_ROM_QSTR(MP_QSTR_create_pen_hsv), MP_ROM_PTR(&ModPicoGraphics_create_pen_hsv_obj) }, { MP_ROM_QSTR(MP_QSTR_update_pen), MP_ROM_PTR(&ModPicoGraphics_update_pen_obj) }, { MP_ROM_QSTR(MP_QSTR_reset_pen), MP_ROM_PTR(&ModPicoGraphics_reset_pen_obj) }, { MP_ROM_QSTR(MP_QSTR_set_palette), MP_ROM_PTR(&ModPicoGraphics_set_palette_obj) }, diff --git a/micropython/modules/picographics/picographics.cpp b/micropython/modules/picographics/picographics.cpp index 84d8de42..20487930 100644 --- a/micropython/modules/picographics/picographics.cpp +++ b/micropython/modules/picographics/picographics.cpp @@ -696,6 +696,21 @@ mp_obj_t ModPicoGraphics_create_pen(size_t n_args, const mp_obj_t *args) { return mp_obj_new_int(result); } +mp_obj_t ModPicoGraphics_create_pen_hsv(size_t n_args, const mp_obj_t *args) { + enum { ARG_self, ARG_h, ARG_s, ARG_v }; + + ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self], ModPicoGraphics_obj_t); + int result = self->graphics->create_pen_hsv( + mp_obj_get_float(args[ARG_h]), + mp_obj_get_float(args[ARG_s]), + mp_obj_get_float(args[ARG_v]) + ); + + if (result == -1) mp_raise_ValueError("create_pen failed. No matching colour or space in palette!"); + + return mp_obj_new_int(result); +} + mp_obj_t ModPicoGraphics_set_palette(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { size_t num_tuples = n_args - 1; const mp_obj_t *tuples = pos_args + 1; diff --git a/micropython/modules/picographics/picographics.h b/micropython/modules/picographics/picographics.h index 9c1a9c15..e46ba904 100644 --- a/micropython/modules/picographics/picographics.h +++ b/micropython/modules/picographics/picographics.h @@ -66,10 +66,12 @@ extern mp_obj_t ModPicoGraphics_set_update_speed(mp_obj_t self_in, mp_obj_t upda 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); // 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); +extern mp_obj_t ModPicoGraphics_create_pen_hsv(size_t n_args, const mp_obj_t *args); // Primitives extern mp_obj_t ModPicoGraphics_set_clip(size_t n_args, const mp_obj_t *args);