diff options
Diffstat (limited to 'circuitpython/shared-module/vectorio')
-rw-r--r-- | circuitpython/shared-module/vectorio/Circle.c | 80 | ||||
-rw-r--r-- | circuitpython/shared-module/vectorio/Circle.h | 18 | ||||
-rw-r--r-- | circuitpython/shared-module/vectorio/Polygon.c | 204 | ||||
-rw-r--r-- | circuitpython/shared-module/vectorio/Polygon.h | 19 | ||||
-rw-r--r-- | circuitpython/shared-module/vectorio/Rectangle.c | 82 | ||||
-rw-r--r-- | circuitpython/shared-module/vectorio/Rectangle.h | 18 | ||||
-rw-r--r-- | circuitpython/shared-module/vectorio/VectorShape.c | 539 | ||||
-rw-r--r-- | circuitpython/shared-module/vectorio/VectorShape.h | 54 | ||||
-rw-r--r-- | circuitpython/shared-module/vectorio/__init__.c | 2 | ||||
-rw-r--r-- | circuitpython/shared-module/vectorio/__init__.h | 14 |
10 files changed, 1030 insertions, 0 deletions
diff --git a/circuitpython/shared-module/vectorio/Circle.c b/circuitpython/shared-module/vectorio/Circle.c new file mode 100644 index 0000000..2ec11fe --- /dev/null +++ b/circuitpython/shared-module/vectorio/Circle.c @@ -0,0 +1,80 @@ + +#include "shared-bindings/vectorio/Circle.h" +#include "shared-module/vectorio/__init__.h" +#include "shared-module/displayio/area.h" + +#include "py/runtime.h" +#include "stdlib.h" + + +void common_hal_vectorio_circle_construct(vectorio_circle_t *self, uint16_t radius, uint16_t color_index) { + self->radius = radius; + self->on_dirty.obj = NULL; + self->color_index = color_index + 1; +} + +void common_hal_vectorio_circle_set_on_dirty(vectorio_circle_t *self, vectorio_event_t on_dirty) { + if (self->on_dirty.obj != NULL) { + mp_raise_TypeError(translate("circle can only be registered in one parent")); + } + self->on_dirty = on_dirty; +} + + +uint32_t common_hal_vectorio_circle_get_pixel(void *obj, int16_t x, int16_t y) { + vectorio_circle_t *self = obj; + int16_t radius = abs(self->radius); + x = abs(x); + y = abs(y); + if (x + y <= radius) { + return self->color_index; + } + if (x > radius) { + return 0; + } + if (y > radius) { + return 0; + } + const bool pythagorasSmallerThanRadius = (int32_t)x * x + (int32_t)y * y <= (int32_t)radius * radius; + return pythagorasSmallerThanRadius ? self->color_index : 0; +} + + +void common_hal_vectorio_circle_get_area(void *circle, displayio_area_t *out_area) { + vectorio_circle_t *self = circle; + out_area->x1 = -1 * self->radius - 1; + out_area->y1 = -1 * self->radius - 1; + out_area->x2 = self->radius + 1; + out_area->y2 = self->radius + 1; +} + +int16_t common_hal_vectorio_circle_get_radius(void *obj) { + vectorio_circle_t *self = obj; + return self->radius; +} + +void common_hal_vectorio_circle_set_radius(void *obj, int16_t radius) { + vectorio_circle_t *self = obj; + self->radius = abs(radius); + if (self->on_dirty.obj != NULL) { + self->on_dirty.event(self->on_dirty.obj); + } +} + +uint16_t common_hal_vectorio_circle_get_color_index(void *obj) { + vectorio_circle_t *self = obj; + return self->color_index - 1; +} + +void common_hal_vectorio_circle_set_color_index(void *obj, uint16_t color_index) { + vectorio_circle_t *self = obj; + self->color_index = abs(color_index + 1); + if (self->on_dirty.obj != NULL) { + self->on_dirty.event(self->on_dirty.obj); + } +} + +mp_obj_t common_hal_vectorio_circle_get_draw_protocol(void *circle) { + vectorio_circle_t *self = circle; + return self->draw_protocol_instance; +} diff --git a/circuitpython/shared-module/vectorio/Circle.h b/circuitpython/shared-module/vectorio/Circle.h new file mode 100644 index 0000000..6ebd9af --- /dev/null +++ b/circuitpython/shared-module/vectorio/Circle.h @@ -0,0 +1,18 @@ +#ifndef MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_CIRCLE_H +#define MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_CIRCLE_H + +#include <stdint.h> + +#include "py/obj.h" + +#include "shared-module/vectorio/__init__.h" + +typedef struct { + mp_obj_base_t base; + uint16_t radius; + uint16_t color_index; + vectorio_event_t on_dirty; + mp_obj_t draw_protocol_instance; +} vectorio_circle_t; + +#endif // MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_CIRCLE_H diff --git a/circuitpython/shared-module/vectorio/Polygon.c b/circuitpython/shared-module/vectorio/Polygon.c new file mode 100644 index 0000000..10ebdf1 --- /dev/null +++ b/circuitpython/shared-module/vectorio/Polygon.c @@ -0,0 +1,204 @@ +#include "shared-module/vectorio/__init__.h" +#include "shared-bindings/vectorio/Polygon.h" +#include "shared-module/displayio/area.h" + +#include "py/runtime.h" +#include "py/gc.h" + +#include "stdlib.h" +#include <stdio.h> + + +#define VECTORIO_POLYGON_DEBUG(...) (void)0 +// #define VECTORIO_POLYGON_DEBUG(...) mp_printf(&mp_plat_print, __VA_ARGS__) + + +// Converts a list of points tuples to a flat list of ints for speedier internal use. +// Also validates the points. +static void _clobber_points_list(vectorio_polygon_t *self, mp_obj_t points_tuple_list) { + size_t len = 0; + mp_obj_t *items; + mp_obj_list_get(points_tuple_list, &len, &items); + VECTORIO_POLYGON_DEBUG(" self.len: %d, len: %d, ", self->len, len); + + if (len < 3) { + mp_raise_TypeError(translate("Polygon needs at least 3 points")); + } + + if (self->len < 2 * len) { + if (self->points_list != NULL) { + VECTORIO_POLYGON_DEBUG("free(%d), ", sizeof(self->points_list)); + gc_free(self->points_list); + } + self->points_list = gc_alloc(2 * len * sizeof(uint16_t), false, false); + VECTORIO_POLYGON_DEBUG("alloc(%p, %d)", self->points_list, 2 * len * sizeof(uint16_t)); + } + self->len = 2 * len; + + for (uint16_t i = 0; i < len; ++i) { + size_t tuple_len = 0; + mp_obj_t *tuple_items; + mp_obj_tuple_get(items[i], &tuple_len, &tuple_items); + + if (tuple_len != 2) { + mp_raise_ValueError_varg(translate("%q must be a tuple of length 2"), MP_QSTR_point); + } + mp_int_t x; + mp_int_t y; + if (!mp_obj_get_int_maybe(tuple_items[ 0 ], &x) + || !mp_obj_get_int_maybe(tuple_items[ 1 ], &y) + || x < SHRT_MIN || x > SHRT_MAX || y < SHRT_MIN || y > SHRT_MAX + ) { + gc_free(self->points_list); + self->points_list = NULL; + mp_raise_ValueError_varg(translate("unsupported %q type"), MP_QSTR_point); + self->len = 0; + } + self->points_list[2 * i ] = (int16_t)x; + self->points_list[2 * i + 1] = (int16_t)y; + } +} + + + +void common_hal_vectorio_polygon_construct(vectorio_polygon_t *self, mp_obj_t points_list, uint16_t color_index) { + VECTORIO_POLYGON_DEBUG("%p polygon_construct: ", self); + self->points_list = NULL; + self->len = 0; + self->on_dirty.obj = NULL; + self->color_index = color_index + 1; + _clobber_points_list(self, points_list); + VECTORIO_POLYGON_DEBUG("\n"); +} + + +mp_obj_t common_hal_vectorio_polygon_get_points(vectorio_polygon_t *self) { + VECTORIO_POLYGON_DEBUG("%p common_hal_vectorio_polygon_get_points {len: %d, points_list: %p}\n", self, self->len, self->points_list); + mp_obj_list_t *list = MP_OBJ_TO_PTR(mp_obj_new_list(0, NULL)); + + VECTORIO_POLYGON_DEBUG(" >points\n"); + for (uint16_t i = 0; i < self->len; i += 2) { + VECTORIO_POLYGON_DEBUG(" (%4d, %4d)\n", self->points_list[i], self->points_list[i + 1]); + + mp_obj_tuple_t *pair = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + pair->items[0] = mp_obj_new_int((mp_int_t)self->points_list[i ]); + pair->items[1] = mp_obj_new_int((mp_int_t)self->points_list[i + 1]); + + mp_obj_list_append( + list, + pair + ); + } + VECTORIO_POLYGON_DEBUG(" <points\n"); + return MP_OBJ_FROM_PTR(list); +} +void common_hal_vectorio_polygon_set_points(vectorio_polygon_t *self, mp_obj_t points_list) { + VECTORIO_POLYGON_DEBUG("%p common_hal_vectorio_polygon_set_points: ", self); + _clobber_points_list(self, points_list); + if (self->on_dirty.obj != NULL) { + self->on_dirty.event(self->on_dirty.obj); + } + VECTORIO_POLYGON_DEBUG("\n"); +} + +void common_hal_vectorio_polygon_set_on_dirty(vectorio_polygon_t *self, vectorio_event_t notification) { + if (self->on_dirty.obj != NULL) { + mp_raise_TypeError(translate("polygon can only be registered in one parent")); + } + self->on_dirty = notification; +} + + +void common_hal_vectorio_polygon_get_area(void *polygon, displayio_area_t *area) { + vectorio_polygon_t *self = polygon; + VECTORIO_POLYGON_DEBUG("%p common_hal_vectorio_polygon_get_area\n"); + + area->x1 = SHRT_MAX; + area->y1 = SHRT_MAX; + area->x2 = SHRT_MIN; + area->y2 = SHRT_MIN; + for (uint16_t i = 0; i < self->len; ++i) { + int16_t x = self->points_list[i]; + ++i; + int16_t y = self->points_list[i]; + if (x < area->x1) { + VECTORIO_POLYGON_DEBUG(" x1: %d\n", x); + area->x1 = x; + } + if (y < area->y1) { + VECTORIO_POLYGON_DEBUG(" y1: %d\n", y); + area->y1 = y; + } + if (x > area->x2) { + VECTORIO_POLYGON_DEBUG(" x2: %d\n", x); + area->x2 = x; + } + if (y > area->y2) { + VECTORIO_POLYGON_DEBUG(" y2: %d\n", y); + area->y2 = y; + } + } +} + + +// <0 if the point is to the left of the line vector +// 0 if the point is on the line +// >0 if the point is to the right of the line vector +__attribute__((always_inline)) static inline int line_side(int16_t x1, int16_t y1, int16_t x2, int16_t y2, int16_t px, int16_t py) { + return (px - x1) * (y2 - y1) + - (py - y1) * (x2 - x1); +} + + +uint32_t common_hal_vectorio_polygon_get_pixel(void *obj, int16_t x, int16_t y) { + VECTORIO_POLYGON_DEBUG("%p polygon get_pixel %d, %d\n", obj, x, y); + vectorio_polygon_t *self = obj; + + if (self->len == 0) { + return 0; + } + + int16_t winding_number = 0; + int16_t x1 = self->points_list[0]; + int16_t y1 = self->points_list[1]; + for (uint16_t i = 2; i <= self->len + 1; ++i) { + VECTORIO_POLYGON_DEBUG(" {(%3d, %3d),", x1, y1); + int16_t x2 = self->points_list[i % self->len]; + ++i; + int16_t y2 = self->points_list[i % self->len]; + VECTORIO_POLYGON_DEBUG(" (%3d, %3d)}\n", x2, y2); + if (y1 <= y) { + if (y2 > y && line_side(x1, y1, x2, y2, x, y) < 0) { + // Wind up, point is to the left of the edge vector + ++winding_number; + VECTORIO_POLYGON_DEBUG(" wind:%2d winding_number:%2d\n", 1, winding_number); + } + } else if (y2 <= y && line_side(x1, y1, x2, y2, x, y) > 0) { + // Wind down, point is to the right of the edge vector + --winding_number; + VECTORIO_POLYGON_DEBUG(" wind:%2d winding_number:%2d\n", -1, winding_number); + } + + x1 = x2; + y1 = y2; + } + return winding_number == 0 ? 0 : self->color_index; +} + +mp_obj_t common_hal_vectorio_polygon_get_draw_protocol(void *polygon) { + vectorio_polygon_t *self = polygon; + return self->draw_protocol_instance; +} + +uint16_t common_hal_vectorio_polygon_get_color_index(void *obj) { + vectorio_polygon_t *self = obj; + return self->color_index - 1; +} + +void common_hal_vectorio_polygon_set_color_index(void *obj, uint16_t color_index) { + vectorio_polygon_t *self = obj; + self->color_index = abs(color_index + 1); + if (self->on_dirty.obj != NULL) { + self->on_dirty.event(self->on_dirty.obj); + } +} diff --git a/circuitpython/shared-module/vectorio/Polygon.h b/circuitpython/shared-module/vectorio/Polygon.h new file mode 100644 index 0000000..795e335 --- /dev/null +++ b/circuitpython/shared-module/vectorio/Polygon.h @@ -0,0 +1,19 @@ +#ifndef MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_POLYGON_H +#define MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_POLYGON_H + +#include <stdint.h> + +#include "py/obj.h" +#include "shared-module/vectorio/__init__.h" + +typedef struct { + mp_obj_base_t base; + // An int array[ x, y, ... ] + int16_t *points_list; + uint16_t len; + uint16_t color_index; + vectorio_event_t on_dirty; + mp_obj_t draw_protocol_instance; +} vectorio_polygon_t; + +#endif // MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_POLYGON_H diff --git a/circuitpython/shared-module/vectorio/Rectangle.c b/circuitpython/shared-module/vectorio/Rectangle.c new file mode 100644 index 0000000..fbd3d6b --- /dev/null +++ b/circuitpython/shared-module/vectorio/Rectangle.c @@ -0,0 +1,82 @@ +#include "shared-module/vectorio/__init__.h" +#include "shared-bindings/vectorio/Rectangle.h" +#include "shared-module/displayio/area.h" + +#include "py/runtime.h" +#include "stdlib.h" + + +void common_hal_vectorio_rectangle_construct(vectorio_rectangle_t *self, uint32_t width, uint32_t height, uint16_t color_index) { + self->width = width; + self->height = height; + self->color_index = color_index + 1; +} + +void common_hal_vectorio_rectangle_set_on_dirty(vectorio_rectangle_t *self, vectorio_event_t on_dirty) { + if (self->on_dirty.obj != NULL) { + mp_raise_TypeError(translate("can only be registered in one parent")); + } + self->on_dirty = on_dirty; +} + +uint32_t common_hal_vectorio_rectangle_get_pixel(void *obj, int16_t x, int16_t y) { + vectorio_rectangle_t *self = obj; + if (x >= 0 && y >= 0 && x < self->width && y < self->height) { + return self->color_index; + } + return 0; +} + + +void common_hal_vectorio_rectangle_get_area(void *rectangle, displayio_area_t *out_area) { + vectorio_rectangle_t *self = rectangle; + out_area->x1 = 0; + out_area->y1 = 0; + out_area->x2 = self->width; + out_area->y2 = self->height; +} + + +mp_obj_t common_hal_vectorio_rectangle_get_draw_protocol(void *rectangle) { + vectorio_rectangle_t *self = rectangle; + return self->draw_protocol_instance; +} + +int16_t common_hal_vectorio_rectangle_get_width(void *obj) { + vectorio_rectangle_t *self = obj; + return self->width; +} + +void common_hal_vectorio_rectangle_set_width(void *obj, int16_t width) { + vectorio_rectangle_t *self = obj; + self->width = abs(width); + if (self->on_dirty.obj != NULL) { + self->on_dirty.event(self->on_dirty.obj); + } +} + +int16_t common_hal_vectorio_rectangle_get_height(void *obj) { + vectorio_rectangle_t *self = obj; + return self->height; +} + +void common_hal_vectorio_rectangle_set_height(void *obj, int16_t height) { + vectorio_rectangle_t *self = obj; + self->height = abs(height); + if (self->on_dirty.obj != NULL) { + self->on_dirty.event(self->on_dirty.obj); + } +} + +uint16_t common_hal_vectorio_rectangle_get_color_index(void *obj) { + vectorio_rectangle_t *self = obj; + return self->color_index - 1; +} + +void common_hal_vectorio_rectangle_set_color_index(void *obj, uint16_t color_index) { + vectorio_rectangle_t *self = obj; + self->color_index = abs(color_index + 1); + if (self->on_dirty.obj != NULL) { + self->on_dirty.event(self->on_dirty.obj); + } +} diff --git a/circuitpython/shared-module/vectorio/Rectangle.h b/circuitpython/shared-module/vectorio/Rectangle.h new file mode 100644 index 0000000..2b1decc --- /dev/null +++ b/circuitpython/shared-module/vectorio/Rectangle.h @@ -0,0 +1,18 @@ +#ifndef MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_RECTANGLE_H +#define MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_RECTANGLE_H + +#include <stdint.h> + +#include "py/obj.h" +#include "shared-module/vectorio/__init__.h" + +typedef struct { + mp_obj_base_t base; + uint16_t width; + uint16_t height; + uint16_t color_index; + vectorio_event_t on_dirty; + mp_obj_t draw_protocol_instance; +} vectorio_rectangle_t; + +#endif // MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_RECTANGLE_H diff --git a/circuitpython/shared-module/vectorio/VectorShape.c b/circuitpython/shared-module/vectorio/VectorShape.c new file mode 100644 index 0000000..04213d3 --- /dev/null +++ b/circuitpython/shared-module/vectorio/VectorShape.c @@ -0,0 +1,539 @@ + +#include "stdlib.h" + +#include "shared-module/vectorio/__init__.h" +#include "shared-bindings/vectorio/VectorShape.h" + +#include "py/misc.h" +#include "py/runtime.h" +#include "shared-bindings/time/__init__.h" +#include "shared-bindings/displayio/ColorConverter.h" +#include "shared-bindings/displayio/Palette.h" + +#include "shared-bindings/vectorio/Circle.h" +#include "shared-bindings/vectorio/Polygon.h" +#include "shared-bindings/vectorio/Rectangle.h" + +// Lifecycle actions. +#define VECTORIO_SHAPE_DEBUG(...) (void)0 +// #define VECTORIO_SHAPE_DEBUG(...) mp_printf(&mp_plat_print, __VA_ARGS__) + + +// Used in both logging and ifdefs, for extra variables +// #define VECTORIO_PERF(...) mp_printf(&mp_plat_print, __VA_ARGS__) + + +// Really verbose. +#define VECTORIO_SHAPE_PIXEL_DEBUG(...) (void)0 +// #define VECTORIO_SHAPE_PIXEL_DEBUG(...) mp_printf(&mp_plat_print, __VA_ARGS__) + +#define U32_TO_BINARY_FMT "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c" +#define U32_TO_BINARY(u32) \ + (u32 & 0x80000000 ? '1' : '0'), \ + (u32 & 0x40000000 ? '1' : '0'), \ + (u32 & 0x20000000 ? '1' : '0'), \ + (u32 & 0x10000000 ? '1' : '0'), \ + (u32 & 0x8000000 ? '1' : '0'), \ + (u32 & 0x4000000 ? '1' : '0'), \ + (u32 & 0x2000000 ? '1' : '0'), \ + (u32 & 0x1000000 ? '1' : '0'), \ + (u32 & 0x800000 ? '1' : '0'), \ + (u32 & 0x400000 ? '1' : '0'), \ + (u32 & 0x200000 ? '1' : '0'), \ + (u32 & 0x100000 ? '1' : '0'), \ + (u32 & 0x80000 ? '1' : '0'), \ + (u32 & 0x40000 ? '1' : '0'), \ + (u32 & 0x20000 ? '1' : '0'), \ + (u32 & 0x10000 ? '1' : '0'), \ + (u32 & 0x8000 ? '1' : '0'), \ + (u32 & 0x4000 ? '1' : '0'), \ + (u32 & 0x2000 ? '1' : '0'), \ + (u32 & 0x1000 ? '1' : '0'), \ + (u32 & 0x800 ? '1' : '0'), \ + (u32 & 0x400 ? '1' : '0'), \ + (u32 & 0x200 ? '1' : '0'), \ + (u32 & 0x100 ? '1' : '0'), \ + (u32 & 0x80 ? '1' : '0'), \ + (u32 & 0x40 ? '1' : '0'), \ + (u32 & 0x20 ? '1' : '0'), \ + (u32 & 0x10 ? '1' : '0'), \ + (u32 & 0x8 ? '1' : '0'), \ + (u32 & 0x4 ? '1' : '0'), \ + (u32 & 0x2 ? '1' : '0'), \ + (u32 & 0x1 ? '1' : '0') + +static void short_bound_check(mp_int_t i, qstr name) { + if (i < SHRT_MIN || i > SHRT_MAX) { + mp_raise_ValueError_varg(translate("%q must be between %d and %d"), name, SHRT_MIN, SHRT_MAX); + } +} + +inline __attribute__((always_inline)) +static void area_transpose(displayio_area_t *to_transpose) { + int16_t swap = to_transpose->y1; + to_transpose->y1 = to_transpose->x1; + to_transpose->x1 = swap; + swap = to_transpose->y2; + to_transpose->y2 = to_transpose->x2; + to_transpose->x2 = swap; +} + +inline __attribute__((always_inline)) +static void _get_screen_area(vectorio_vector_shape_t *self, displayio_area_t *out_area) { + VECTORIO_SHAPE_DEBUG("%p get_screen_area (%3d,%3d) tform:{x:%d y:%d dx:%d dy:%d scl:%d w:%d h:%d mx:%d my:%d tr:%d}", self, self->x, self->y, + self->absolute_transform->x, self->absolute_transform->y, self->absolute_transform->dx, self->absolute_transform->dy, self->absolute_transform->scale, + self->absolute_transform->width, self->absolute_transform->height, self->absolute_transform->mirror_x, self->absolute_transform->mirror_y, self->absolute_transform->transpose_xy + ); + self->ishape.get_area(self->ishape.shape, out_area); + VECTORIO_SHAPE_DEBUG(" in:{(%5d,%5d), (%5d,%5d)}", out_area->x1, out_area->y1, out_area->x2, out_area->y2); + + int16_t x; + int16_t y; + if (self->absolute_transform->transpose_xy) { + x = self->absolute_transform->x + self->absolute_transform->dx * self->y; + y = self->absolute_transform->y + self->absolute_transform->dy * self->x; + if (self->absolute_transform->dx < 1) { + out_area->y1 = out_area->y1 * -1 + 1; + out_area->y2 = out_area->y2 * -1 + 1; + } + if (self->absolute_transform->dy < 1) { + out_area->x1 = out_area->x1 * -1 + 1; + out_area->x2 = out_area->x2 * -1 + 1; + } + area_transpose(out_area); + } else { + x = self->absolute_transform->x + self->absolute_transform->dx * self->x; + y = self->absolute_transform->y + self->absolute_transform->dy * self->y; + + if (self->absolute_transform->dx < 1) { + out_area->x1 = out_area->x1 * -1 + 1; + out_area->x2 = out_area->x2 * -1 + 1; + } + if (self->absolute_transform->dy < 1) { + out_area->y1 = out_area->y1 * -1 + 1; + out_area->y2 = out_area->y2 * -1 + 1; + } + } + displayio_area_canon(out_area); + displayio_area_shift(out_area, x, y); + + VECTORIO_SHAPE_DEBUG(" out:{(%5d,%5d), (%5d,%5d)}\n", out_area->x1, out_area->y1, out_area->x2, out_area->y2); +} + +// Get the target pixel based on the shape's coordinate space +static void screen_to_shape_coordinates(vectorio_vector_shape_t *self, uint16_t x, uint16_t y, int16_t *out_shape_x, int16_t *out_shape_y) { + if (self->absolute_transform->transpose_xy) { + *out_shape_x = y - self->absolute_transform->y - self->absolute_transform->dy * self->x; + *out_shape_y = x - self->absolute_transform->x - self->absolute_transform->dx * self->y; + + VECTORIO_SHAPE_PIXEL_DEBUG(" a(%3d, %3d)", *out_shape_x, *out_shape_y); + if (self->absolute_transform->dx < 1) { + *out_shape_y *= -1; + } + if (self->absolute_transform->dy < 1) { + *out_shape_x *= -1; + } + VECTORIO_SHAPE_PIXEL_DEBUG(" b(%3d, %3d)", *out_shape_x, *out_shape_y); + } else { + *out_shape_x = x - self->absolute_transform->x - self->absolute_transform->dx * self->x; + *out_shape_y = y - self->absolute_transform->y - self->absolute_transform->dy * self->y; + + VECTORIO_SHAPE_PIXEL_DEBUG(" a(%3d, %3d)", *out_shape_x, *out_shape_y); + if (self->absolute_transform->dx < 1) { + *out_shape_x *= -1; + } + if (self->absolute_transform->dy < 1) { + *out_shape_y *= -1; + } + VECTORIO_SHAPE_PIXEL_DEBUG(" b(%3d, %3d)", *out_shape_x, *out_shape_y); + + // It's mirrored via dx. Maybe we need to add support for also separately mirroring? + // if (self->absolute_transform->mirror_x) { + // pixel_to_get_x = (shape_area.x2 - shape_area.x1) - (pixel_to_get_x - shape_area.x1) + shape_area.x1 - 1; + // } + // if (self->absolute_transform->mirror_y) { + // pixel_to_get_y = (shape_area.y2 - shape_area.y1) - (pixel_to_get_y - shape_area.y1) + +shape_area.y1 - 1; + // } + } +} + +static void check_bounds_and_set_x(vectorio_vector_shape_t *self, mp_int_t x) { + short_bound_check(x, MP_QSTR_x); + self->x = x; +} + +static void check_bounds_and_set_y(vectorio_vector_shape_t *self, mp_int_t y) { + short_bound_check(y, MP_QSTR_y); + self->y = y; +} + + +// For use by Group to know where it needs to redraw on layer removal. +bool vectorio_vector_shape_get_dirty_area(vectorio_vector_shape_t *self, displayio_area_t *out_area) { + out_area->x1 = out_area->x2; + displayio_area_union( + &self->ephemeral_dirty_area, + &self->current_area, + out_area + ); + return true; // For now just always redraw. +} + + +// This must be invoked after each time a shape changes its position, shape or appearance in any way. +void common_hal_vectorio_vector_shape_set_dirty(void *vector_shape) { + vectorio_vector_shape_t *self = vector_shape; + // In screen space. Need to offset the shape space. + displayio_area_t current_area; + _get_screen_area(self, ¤t_area); + VECTORIO_SHAPE_DEBUG("%p shape_dirty new:{(%3d,%3d), (%3d,%3d)} dirty:{(%3d,%3d), (%3d,%3d)}", + self, + current_area.x1, current_area.y1, current_area.x2, current_area.y2, + self->ephemeral_dirty_area.x1, self->ephemeral_dirty_area.y1, self->ephemeral_dirty_area.x2, self->ephemeral_dirty_area.y2); + + bool moved = !displayio_area_equal(¤t_area, &self->current_area); + if (moved) { + displayio_area_union(&self->current_area, &self->ephemeral_dirty_area, &self->ephemeral_dirty_area); + VECTORIO_SHAPE_DEBUG(" stale:{(%3d,%3d), (%3d,%3d)} -> expanded:{(%3d,%3d), (%3d,%3d)}\n", + self->current_area.x1, self->current_area.y1, self->current_area.x2, self->current_area.y2, + self->ephemeral_dirty_area.x1, self->ephemeral_dirty_area.y1, self->ephemeral_dirty_area.x2, self->ephemeral_dirty_area.y2); + + // Dirty area tracks the shape's footprint between draws. It's reset on refresh finish. + displayio_area_copy(¤t_area, &self->current_area); + } + self->current_area_dirty = true; +} + + +void common_hal_vectorio_vector_shape_construct(vectorio_vector_shape_t *self, + vectorio_ishape_t ishape, + mp_obj_t pixel_shader, int32_t x, int32_t y) { + VECTORIO_SHAPE_DEBUG("%p vector_shape_construct x:%3d, y:%3d\n", self, x, y); + check_bounds_and_set_x(self, x); + check_bounds_and_set_y(self, y); + self->pixel_shader = pixel_shader; + self->ishape = ishape; + self->absolute_transform = &null_transform; // Critical to have a valid transform before getting screen area. + self->ephemeral_dirty_area.x1 = self->ephemeral_dirty_area.x2; // Cheat to set area to 0 + self->ephemeral_dirty_area.next = NULL; + self->current_area_dirty = true; + _get_screen_area(self, &self->current_area); +} + +bool common_hal_vectorio_vector_shape_contains(vectorio_vector_shape_t *self, mp_int_t x, mp_int_t y) { + VECTORIO_SHAPE_DEBUG("%p contains(%d, %d)", self); + short_bound_check(x, MP_QSTR_x); + short_bound_check(y, MP_QSTR_y); + int16_t shape_x; + int16_t shape_y; + screen_to_shape_coordinates(self, x, y, &shape_x, &shape_y); + bool shape_contains_coordinates = 0 != self->ishape.get_pixel(self->ishape.shape, shape_x, shape_y); + return shape_contains_coordinates; +} + + +mp_int_t common_hal_vectorio_vector_shape_get_x(vectorio_vector_shape_t *self) { + VECTORIO_SHAPE_DEBUG("%p get_x\n", self); + return self->x; +} + + +void common_hal_vectorio_vector_shape_set_x(vectorio_vector_shape_t *self, mp_int_t x) { + VECTORIO_SHAPE_DEBUG("%p set_x %d\n", self, x); + if (self->x == x) { + return; + } + check_bounds_and_set_x(self, x); + common_hal_vectorio_vector_shape_set_dirty(self); +} + + +mp_int_t common_hal_vectorio_vector_shape_get_y(vectorio_vector_shape_t *self) { + VECTORIO_SHAPE_DEBUG("%p get_y\n", self); + return self->y; +} + + +void common_hal_vectorio_vector_shape_set_y(vectorio_vector_shape_t *self, mp_int_t y) { + VECTORIO_SHAPE_DEBUG("%p set_y %d\n", self, y); + if (self->y == y) { + return; + } + check_bounds_and_set_y(self, y); + common_hal_vectorio_vector_shape_set_dirty(self); +} + +mp_obj_tuple_t *common_hal_vectorio_vector_shape_get_location(vectorio_vector_shape_t *self) { + VECTORIO_SHAPE_DEBUG("%p get_location\n", self); + mp_obj_tuple_t *pair = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + pair->items[0] = mp_obj_new_int((mp_int_t)self->x); + pair->items[1] = mp_obj_new_int((mp_int_t)self->y); + return pair; +} + + +void common_hal_vectorio_vector_shape_set_location(vectorio_vector_shape_t *self, mp_obj_t xy) { + VECTORIO_SHAPE_DEBUG("%p set_location\n", self); + size_t tuple_len = 0; + mp_obj_t *tuple_items; + mp_obj_tuple_get(xy, &tuple_len, &tuple_items); + if (tuple_len != 2) { + mp_raise_TypeError(translate("(x,y) integers required")); + } + + mp_int_t x; + mp_int_t y; + if (!mp_obj_get_int_maybe(tuple_items[ 0 ], &x) + || !mp_obj_get_int_maybe(tuple_items[ 1 ], &y)) { + mp_raise_ValueError_varg(translate("unsupported %q type"), MP_QSTR_point); + } + bool dirty = false; + if (self->x != x) { + check_bounds_and_set_x(self, x); + dirty = true; + } + if (self->y != y) { + check_bounds_and_set_y(self, y); + dirty = true; + } + if (dirty) { + common_hal_vectorio_vector_shape_set_dirty(self); + } +} + + +mp_obj_t common_hal_vectorio_vector_shape_get_pixel_shader(vectorio_vector_shape_t *self) { + VECTORIO_SHAPE_DEBUG("%p get_pixel_shader\n", self); + return self->pixel_shader; +} + +void common_hal_vectorio_vector_shape_set_pixel_shader(vectorio_vector_shape_t *self, mp_obj_t pixel_shader) { + VECTORIO_SHAPE_DEBUG("%p set_pixel_shader\n", self); + self->pixel_shader = pixel_shader; + common_hal_vectorio_vector_shape_set_dirty(self); +} + +bool vectorio_vector_shape_fill_area(vectorio_vector_shape_t *self, const _displayio_colorspace_t *colorspace, const displayio_area_t *area, uint32_t *mask, uint32_t *buffer) { + // Shape areas are relative to 0,0. This will allow rotation about a known axis. + // The consequence is that the area reported by the shape itself is _relative_ to 0,0. + // To make it relative to the VectorShape position, we must shift it. + // Pixels are drawn on the screen_area (shifted) coordinate space, while pixels are _determined_ from + // the shape_area (unshifted) space. + #ifdef VECTORIO_PERF + uint64_t start = common_hal_time_monotonic_ns(); + uint64_t pixel_time = 0; + #endif + VECTORIO_SHAPE_DEBUG("%p fill_area: fill: {(%5d,%5d), (%5d,%5d)}", + self, + area->x1, area->y1, area->x2, area->y2 + ); + displayio_area_t overlap; + if (!displayio_area_compute_overlap(area, &self->current_area, &overlap)) { + VECTORIO_SHAPE_DEBUG(" no overlap\n"); + return false; + } + VECTORIO_SHAPE_DEBUG(", overlap: {(%3d,%3d), (%3d,%3d)}", overlap.x1, overlap.y1, overlap.x2, overlap.y2); + + bool full_coverage = displayio_area_equal(area, &overlap); + + uint8_t pixels_per_byte = 8 / colorspace->depth; + VECTORIO_SHAPE_DEBUG(" xy:(%3d %3d) tform:{x:%d y:%d dx:%d dy:%d scl:%d w:%d h:%d mx:%d my:%d tr:%d}", + self->x, self->y, + self->absolute_transform->x, self->absolute_transform->y, self->absolute_transform->dx, self->absolute_transform->dy, self->absolute_transform->scale, + self->absolute_transform->width, self->absolute_transform->height, self->absolute_transform->mirror_x, self->absolute_transform->mirror_y, self->absolute_transform->transpose_xy + ); + + uint16_t linestride_px = displayio_area_width(area); + uint16_t line_dirty_offset_px = (overlap.y1 - area->y1) * linestride_px; + uint16_t column_dirty_offset_px = overlap.x1 - area->x1; + VECTORIO_SHAPE_DEBUG(", linestride:%3d line_offset:%3d col_offset:%3d depth:%2d ppb:%2d shape:%s", + linestride_px, line_dirty_offset_px, column_dirty_offset_px, colorspace->depth, pixels_per_byte, mp_obj_get_type_str(self->ishape.shape)); + + displayio_input_pixel_t input_pixel; + displayio_output_pixel_t output_pixel; + + displayio_area_t shape_area; + self->ishape.get_area(self->ishape.shape, &shape_area); + + uint16_t mask_start_px = line_dirty_offset_px; + for (input_pixel.y = overlap.y1; input_pixel.y < overlap.y2; ++input_pixel.y) { + mask_start_px += column_dirty_offset_px; + for (input_pixel.x = overlap.x1; input_pixel.x < overlap.x2; ++input_pixel.x) { + // Check the mask first to see if the pixel has already been set. + uint16_t pixel_index = mask_start_px + (input_pixel.x - overlap.x1); + uint32_t *mask_doubleword = &(mask[pixel_index / 32]); + uint8_t mask_bit = pixel_index % 32; + VECTORIO_SHAPE_PIXEL_DEBUG("\n%p pixel_index: %5u mask_bit: %2u mask: "U32_TO_BINARY_FMT, self, pixel_index, mask_bit, U32_TO_BINARY(*mask_doubleword)); + if ((*mask_doubleword & (1u << mask_bit)) != 0) { + VECTORIO_SHAPE_PIXEL_DEBUG(" masked"); + continue; + } + output_pixel.pixel = 0; + + // Cast input screen coordinates to shape coordinates to pick the pixel to draw + int16_t pixel_to_get_x; + int16_t pixel_to_get_y; + screen_to_shape_coordinates(self, input_pixel.x, input_pixel.y, &pixel_to_get_x, &pixel_to_get_y); + + VECTORIO_SHAPE_PIXEL_DEBUG(" get_pixel %p (%3d, %3d) -> ( %3d, %3d )", self->ishape.shape, input_pixel.x, input_pixel.y, pixel_to_get_x, pixel_to_get_y); + #ifdef VECTORIO_PERF + uint64_t pre_pixel = common_hal_time_monotonic_ns(); + #endif + input_pixel.pixel = self->ishape.get_pixel(self->ishape.shape, pixel_to_get_x, pixel_to_get_y); + #ifdef VECTORIO_PERF + uint64_t post_pixel = common_hal_time_monotonic_ns(); + pixel_time += post_pixel - pre_pixel; + #endif + VECTORIO_SHAPE_PIXEL_DEBUG(" -> %d", input_pixel.pixel); + + // vectorio shapes use 0 to mean "area is not covered." + // We can skip all the rest of the work for this pixel if it's not currently covered by the shape. + if (input_pixel.pixel == 0) { + VECTORIO_SHAPE_PIXEL_DEBUG(" (encountered transparent pixel; input area is not fully covered)"); + full_coverage = false; + } else { + // Pixel is not transparent. Let's pull the pixel value index down to 0-base for more error-resistant palettes. + input_pixel.pixel -= 1; + output_pixel.opaque = true; + + if (self->pixel_shader == mp_const_none) { + output_pixel.pixel = input_pixel.pixel; + } else if (mp_obj_is_type(self->pixel_shader, &displayio_palette_type)) { + output_pixel.opaque = displayio_palette_get_color(self->pixel_shader, colorspace, input_pixel.pixel, &output_pixel.pixel); + } else if (mp_obj_is_type(self->pixel_shader, &displayio_colorconverter_type)) { + displayio_colorconverter_convert(self->pixel_shader, colorspace, &input_pixel, &output_pixel); + } + + // We double-check this to fast-path the case when a pixel is not covered by the shape & not call the color converter unnecessarily. + if (!output_pixel.opaque) { + VECTORIO_SHAPE_PIXEL_DEBUG(" (encountered transparent pixel from colorconverter; input area is not fully covered)"); + full_coverage = false; + } + + *mask_doubleword |= 1u << mask_bit; + if (colorspace->depth == 16) { + VECTORIO_SHAPE_PIXEL_DEBUG(" buffer = %04x 16", output_pixel.pixel); + *(((uint16_t *)buffer) + pixel_index) = output_pixel.pixel; + } else if (colorspace->depth == 32) { + VECTORIO_SHAPE_PIXEL_DEBUG(" buffer = %04x 32", output_pixel.pixel); + *(((uint32_t *)buffer) + pixel_index) = output_pixel.pixel; + } else if (colorspace->depth == 8) { + VECTORIO_SHAPE_PIXEL_DEBUG(" buffer = %02x 8", output_pixel.pixel); + *(((uint8_t *)buffer) + pixel_index) = output_pixel.pixel; + } else if (colorspace->depth < 8) { + // Reorder the offsets to pack multiple rows into a byte (meaning they share a column). + if (!colorspace->pixels_in_byte_share_row) { + uint16_t row = pixel_index / linestride_px; + uint16_t col = pixel_index % linestride_px; + pixel_index = col * pixels_per_byte + (row / pixels_per_byte) * pixels_per_byte * linestride_px + row % pixels_per_byte; + } + uint8_t shift = (pixel_index % pixels_per_byte) * colorspace->depth; + if (colorspace->reverse_pixels_in_byte) { + // Reverse the shift by subtracting it from the leftmost shift. + shift = (pixels_per_byte - 1) * colorspace->depth - shift; + } + VECTORIO_SHAPE_PIXEL_DEBUG(" buffer = %2d %d", output_pixel.pixel, colorspace->depth); + ((uint8_t *)buffer)[pixel_index / pixels_per_byte] |= output_pixel.pixel << shift; + } + } + } + mask_start_px += linestride_px - column_dirty_offset_px; + } + #ifdef VECTORIO_PERF + uint64_t end = common_hal_time_monotonic_ns(); + uint32_t pixels = (overlap.x2 - overlap.x1) * (overlap.y2 - overlap.y1); + VECTORIO_PERF("draw %16s -> shape:{%4dpx, %4.1fms,%9.1fpps fill} shape_pixels:{%6.1fus total, %4.1fus/px}\n", + mp_obj_get_type_str(self->ishape.shape), + (overlap.x2 - overlap.x1) * (overlap.y2 - overlap.y1), + (double)((end - start) / 1000000.0), + (double)(MAX(1, pixels * (1000000000.0 / (end - start)))), + (double)(pixel_time / 1000.0), + (double)(pixel_time / 1000.0 / pixels) + ); + #endif + VECTORIO_SHAPE_DEBUG(" -> pixels:%4d\n", (overlap.x2 - overlap.x1) * (overlap.y2 - overlap.y1)); + return full_coverage; +} + + +void vectorio_vector_shape_finish_refresh(vectorio_vector_shape_t *self) { + if (displayio_area_empty(&self->ephemeral_dirty_area) && !self->current_area_dirty) { + return; + } + VECTORIO_SHAPE_DEBUG("%p finish_refresh was:{(%3d,%3d), (%3d,%3d)}\n", self, self->ephemeral_dirty_area.x1, self->ephemeral_dirty_area.y1, self->ephemeral_dirty_area.x2, self->ephemeral_dirty_area.y2); + // Reset dirty area to nothing + self->ephemeral_dirty_area.x1 = self->ephemeral_dirty_area.x2; // Cheat to set area to empty + self->ephemeral_dirty_area.next = NULL; + + self->current_area_dirty = false; // We don't clear current area so we can remember what to clean up if we move + self->current_area.next = NULL; + + VECTORIO_SHAPE_DEBUG("%p finish_refresh now:{(%3d,%3d), (%3d,%3d)}\n", self, self->ephemeral_dirty_area.x1, self->ephemeral_dirty_area.y1, self->ephemeral_dirty_area.x2, self->ephemeral_dirty_area.y2); + + if (mp_obj_is_type(self->pixel_shader, &displayio_palette_type)) { + displayio_palette_finish_refresh(self->pixel_shader); + } else if (mp_obj_is_type(self->pixel_shader, &displayio_colorconverter_type)) { + displayio_colorconverter_finish_refresh(self->pixel_shader); + } +} + + +// Assembles a singly linked list of dirty areas from all components on the display. +displayio_area_t *vectorio_vector_shape_get_refresh_areas(vectorio_vector_shape_t *self, displayio_area_t *tail) { + if (self->current_area_dirty + || (mp_obj_is_type(self->pixel_shader, &displayio_palette_type) && displayio_palette_needs_refresh(self->pixel_shader)) + || (mp_obj_is_type(self->pixel_shader, &displayio_colorconverter_type) && displayio_colorconverter_needs_refresh(self->pixel_shader)) + ) { + if (!displayio_area_empty(&self->ephemeral_dirty_area)) { + // Both are dirty, check if we should combine the areas or draw separately + // Draws as few pixels as possible both when animations move short distances and large distances. + // The display core implementation currently doesn't combine areas to reduce redrawing of masked areas. If it does, + // this could be simplified to just return the 2 possibly overlapping areas. + displayio_area_t area_swap; + displayio_area_compute_overlap(&self->ephemeral_dirty_area, &self->current_area, &area_swap); + uint32_t overlap_size = displayio_area_size(&area_swap); + displayio_area_union(&self->ephemeral_dirty_area, &self->current_area, &area_swap); // Leave area_swap as the union area for later. + uint32_t union_size = displayio_area_size(&area_swap); + uint32_t current_size = displayio_area_size(&self->current_area); + uint32_t dirty_size = displayio_area_size(&self->ephemeral_dirty_area); + + VECTORIO_SHAPE_DEBUG("%p get_refresh_area: dirty{(%3d,%3d), (%3d,%3d)} + current{(%3d,%3d), (%3d,%3d)} = union{(%3d,%3d), (%3d,%3d)}: union%d - dirty%d - curr%d + overlap%d = excluded%d : ", self, + self->ephemeral_dirty_area.x1, self->ephemeral_dirty_area.y1, self->ephemeral_dirty_area.x2, self->ephemeral_dirty_area.y2, + self->current_area.x1, self->current_area.y1, self->current_area.x2, self->current_area.y2, + area_swap.x1, area_swap.y1, area_swap.x2, area_swap.y2, + union_size, dirty_size, current_size, overlap_size, (int32_t)union_size - dirty_size - current_size + overlap_size + ); + + if ((int32_t)union_size - dirty_size - current_size + overlap_size <= MIN(dirty_size, current_size)) { + // The excluded / non-overlapping area from the disjoint dirty and current areas is smaller + // than the smallest area we need to draw. Redrawing the overlapping area would cost more + // than just drawing the union disjoint area once. + VECTORIO_SHAPE_DEBUG("combining to take disjoint area\n"); + displayio_area_copy(&area_swap, &self->ephemeral_dirty_area); + } else { + // The excluded area between the 2 dirty areas is larger than the smallest dirty area. It would be + // more costly to combine these areas than possibly redraw some overlap. + VECTORIO_SHAPE_DEBUG("excluded area too large, drawing separate area\n"); + self->current_area.next = tail; + tail = &self->current_area; + } + + self->ephemeral_dirty_area.next = tail; + tail = &self->ephemeral_dirty_area; + } else { + self->current_area.next = tail; + tail = &self->current_area; + VECTORIO_SHAPE_DEBUG("%p get_refresh_area: redrawing current: {(%3d,%3d), (%3d,%3d)}\n", self, self->current_area.x1, self->current_area.y1, self->current_area.x2, self->current_area.y2); + } + } else if (!displayio_area_empty(&self->ephemeral_dirty_area)) { + self->ephemeral_dirty_area.next = tail; + tail = &self->ephemeral_dirty_area; + VECTORIO_SHAPE_DEBUG("%p get_refresh_area redrawing dirty: {(%3d,%3d), (%3d,%3d)}\n", self, self->ephemeral_dirty_area.x1, self->ephemeral_dirty_area.y1, self->ephemeral_dirty_area.x2, self->ephemeral_dirty_area.y2); + } + return tail; +} + +void vectorio_vector_shape_update_transform(vectorio_vector_shape_t *self, displayio_buffer_transform_t *group_transform) { + self->absolute_transform = group_transform == NULL ? &null_transform : group_transform; + common_hal_vectorio_vector_shape_set_dirty(self); +} diff --git a/circuitpython/shared-module/vectorio/VectorShape.h b/circuitpython/shared-module/vectorio/VectorShape.h new file mode 100644 index 0000000..fdbae96 --- /dev/null +++ b/circuitpython/shared-module/vectorio/VectorShape.h @@ -0,0 +1,54 @@ + +#ifndef MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_SHAPE_H +#define MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_SHAPE_H + +#include <stdbool.h> +#include <stdint.h> + +#include "py/obj.h" +#include "shared-module/displayio/area.h" +#include "shared-module/displayio/Palette.h" + +typedef void get_area_function(mp_obj_t shape, displayio_area_t *out_area); +typedef uint32_t get_pixel_function(mp_obj_t shape, int16_t x, int16_t y); + +// This struct binds a shape's common Shape support functions (its vector shape interface) +// to its instance pointer. We only check at construction time what the type of the +// associated shape is and link the correct functions up. +// Later when using the shape for drawing logic these functions may be invoked +// unconditionally. This simplifies the addition of new types and restricts the +// respective responsibilities of VectorShape and actual shape implementations. +typedef struct { + mp_obj_t shape; + get_area_function *get_area; + get_pixel_function *get_pixel; +} vectorio_ishape_t; + +typedef struct { + mp_obj_base_t base; + vectorio_ishape_t ishape; + mp_obj_t pixel_shader; + int16_t x; + int16_t y; + displayio_buffer_transform_t *absolute_transform; + // Tracks current shape footprint and expands outward as the shape dirties and changes. + // This is suboptimal if you move your shape far. Could add more state to only redraw + // exactly what we left behind. + displayio_area_t ephemeral_dirty_area; + displayio_area_t current_area; + bool current_area_dirty; +} vectorio_vector_shape_t; + +displayio_area_t *vectorio_vector_shape_get_refresh_areas(vectorio_vector_shape_t *self, displayio_area_t *tail); + +bool vectorio_vector_shape_get_dirty_area(vectorio_vector_shape_t *self, displayio_area_t *current_dirty_area); + +// Area is always in absolute screen coordinates. +bool vectorio_vector_shape_fill_area(vectorio_vector_shape_t *self, const _displayio_colorspace_t *colorspace, const displayio_area_t *area, uint32_t *mask, uint32_t *buffer); + +// Fills in out_area with the maximum bounds of all related pixels in the last rendered frame. Returns +// false if the vector shape wasn't rendered in the last frame. +bool vectorio_vector_shape_get_previous_area(vectorio_vector_shape_t *self, displayio_area_t *out_area); +void vectorio_vector_shape_finish_refresh(vectorio_vector_shape_t *self); + +#endif // MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_SHAPE_H diff --git a/circuitpython/shared-module/vectorio/__init__.c b/circuitpython/shared-module/vectorio/__init__.c new file mode 100644 index 0000000..f5227ef --- /dev/null +++ b/circuitpython/shared-module/vectorio/__init__.c @@ -0,0 +1,2 @@ + +// Don't need anything in here yet diff --git a/circuitpython/shared-module/vectorio/__init__.h b/circuitpython/shared-module/vectorio/__init__.h new file mode 100644 index 0000000..8da85bb --- /dev/null +++ b/circuitpython/shared-module/vectorio/__init__.h @@ -0,0 +1,14 @@ +#ifndef MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_INIT_H +#define MICROPY_INCLUDED_SHARED_MODULE_VECTORIO_INIT_H + +#include "py/obj.h" + +typedef void event_function(mp_obj_t obj); + +typedef struct { + mp_obj_t obj; + event_function *event; +} vectorio_event_t; + + +#endif |