From b47dbe2c15d88ad71188296466e4fcbc48bf33c3 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Mon, 31 Dec 2018 08:32:13 -0800 Subject: [PATCH] Kwabena/drawing updates (#422) * Add ellipse drawing function. --- .../examples/03-Drawing/ellipse_drawing.py | 35 ++++ scripts/examples/03-Drawing/image_drawing.py | 14 +- src/omv/boards/OPENMV2/imlib_config.h | 2 +- src/omv/img/draw.c | 160 +++++++++++++++++- src/omv/img/imlib.h | 1 + src/omv/py/py_image.c | 28 ++- src/omv/py/qstrdefsomv.h | 9 +- 7 files changed, 237 insertions(+), 12 deletions(-) create mode 100644 scripts/examples/03-Drawing/ellipse_drawing.py diff --git a/scripts/examples/03-Drawing/ellipse_drawing.py b/scripts/examples/03-Drawing/ellipse_drawing.py new file mode 100644 index 000000000..e824a1180 --- /dev/null +++ b/scripts/examples/03-Drawing/ellipse_drawing.py @@ -0,0 +1,35 @@ +# Ellipse Drawing +# +# This example shows off drawing ellipses on the OpenMV Cam. + +import sensor, image, time, pyb + +sensor.reset() +sensor.set_pixformat(sensor.RGB565) # or GRAYSCALE... +sensor.set_framesize(sensor.QVGA) # or QQVGA... +sensor.skip_frames(time = 2000) +clock = time.clock() + +while(True): + clock.tick() + + img = sensor.snapshot() + + for i in range(10): + x = (pyb.rng() % (2*img.width())) - (img.width()//2) + y = (pyb.rng() % (2*img.height())) - (img.height()//2) + radius_x = pyb.rng() % (max(img.height(), img.width())//2) + radius_y = pyb.rng() % (max(img.height(), img.width())//2) + rot = pyb.rng() % 45 # Avoid large rotation angles with fill on... + + r = (pyb.rng() % 127) + 128 + g = (pyb.rng() % 127) + 128 + b = (pyb.rng() % 127) + 128 + + # If the first argument is a scaler then this method expects + # to see x, y, radius x, and radius y. + # Otherwise, it expects a (x,y,radius_x,radius_y) tuple. + img.draw_ellipse(x, y, radius_x, radius_y, \ + rotation = rot, color = (r, g, b), thickness = 2, fill = False) + + print(clock.fps()) diff --git a/scripts/examples/03-Drawing/image_drawing.py b/scripts/examples/03-Drawing/image_drawing.py index 1c30fde49..5931c394d 100644 --- a/scripts/examples/03-Drawing/image_drawing.py +++ b/scripts/examples/03-Drawing/image_drawing.py @@ -14,12 +14,12 @@ while(True): clock.tick() img = sensor.snapshot() - w = img.width() - h = img.height() - # Draws an image in the frame buffer. In this case we're - # drawing the image we're currently drawing which causes - # graphical glitches but is cool. Pass an optional mask - # image to control what pixels are drawn. - img.draw_image(img, w//4, h//4, x_scale=0.5, y_scale=0.5) + small_img = img.mean_pooled(4, 4) # Makes a copy. + + x = (img.width()//2)-(small_img.width()//2) + y = (img.height()//2)-(small_img.height()//2) + # Draws an image in the frame buffer.Pass an optional + # mask image to control what pixels are drawn. + img.draw_image(small_img, x, y, x_scale=1, y_scale=1) print(clock.fps()) diff --git a/src/omv/boards/OPENMV2/imlib_config.h b/src/omv/boards/OPENMV2/imlib_config.h index 1bd1ebec2..a2a76ad3e 100644 --- a/src/omv/boards/OPENMV2/imlib_config.h +++ b/src/omv/boards/OPENMV2/imlib_config.h @@ -31,7 +31,7 @@ //#define IMLIB_ENABLE_MIDPOINT // Enable Gaussian -#define IMLIB_ENABLE_GAUSSIAN +//#define IMLIB_ENABLE_GAUSSIAN // Enable Laplacian //#define IMLIB_ENABLE_LAPLACIAN diff --git a/src/omv/img/draw.c b/src/omv/img/draw.c index d9ccdc019..dcfc35f6f 100644 --- a/src/omv/img/draw.c +++ b/src/omv/img/draw.c @@ -170,6 +170,104 @@ void imlib_draw_circle(image_t *img, int cx, int cy, int r, int c, int thickness } } +// https://scratch.mit.edu/projects/50039326/ +static void scratch_draw_pixel(image_t *img, int x0, int y0, int dx, int dy, float shear_dx, float shear_dy, int r0, int r1, int c) +{ + point_fill(img, x0 + dx, y0 + dy + fast_floorf((dx * shear_dy) / shear_dx), r0, r1, c); +} + +// https://scratch.mit.edu/projects/50039326/ +static void scratch_draw_line(image_t *img, int x0, int y0, int dx, int dy0, int dy1, float shear_dx, float shear_dy, int c) +{ + imlib_draw_line(img, x0 + dx, y0 + dy0 + fast_floorf((dx * shear_dy) / shear_dx), + x0 + dx, y0 + dy1 + fast_floorf((dx * shear_dy) / shear_dx), c, 1); +} + +// https://scratch.mit.edu/projects/50039326/ +static void scratch_draw_sheared_ellipse(image_t *img, int x0, int y0, int width, int height, bool filled, float shear_dx, float shear_dy, int c, int thickness) +{ + int thickness0 = (thickness - 0) / 2; + int thickness1 = (thickness - 1) / 2; + if (((thickness > 0) || filled) && (shear_dx != 0)) { + int a_squared = width * width; + int four_a_squared = a_squared * 4; + int b_squared = height * height; + int four_b_squared = b_squared * 4; + + int x = 0; + int y = height; + int sigma = (2 * b_squared) + (a_squared * (1 - (2 * height))); + + while ((b_squared * x) <= (a_squared * y)) { + if (filled) { + scratch_draw_line(img, x0, y0, x, -y, y, shear_dx, shear_dy, c); + scratch_draw_line(img, x0, y0, -x, -y, y, shear_dx, shear_dy, c); + } else { + scratch_draw_pixel(img, x0, y0, x, y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, -x, y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, -x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); + } + + if (sigma >= 0) { + sigma += four_a_squared * (1 - y); + y -= 1; + } + + sigma += b_squared * ((4 * x) + 6); + x += 1; + } + + x = width; + y = 0; + sigma = (2 * a_squared) + (b_squared * (1 - (2 * width))); + + while ((a_squared * y) <= (b_squared * x)) { + if (filled) { + scratch_draw_line(img, x0, y0, x, -y, y, shear_dx, shear_dy, c); + scratch_draw_line(img, x0, y0, -x, -y, y, shear_dx, shear_dy, c); + } else { + scratch_draw_pixel(img, x0, y0, x, y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, -x, y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, -x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); + } + + if (sigma >= 0) { + sigma += four_b_squared * (1 - x); + x -= 1; + } + + sigma += a_squared * ((4 * y) + 6); + y += 1; + } + } +} + +// https://scratch.mit.edu/projects/50039326/ +static void scratch_draw_rotated_ellipse(image_t *img, int x, int y, int x_axis, int y_axis, int rotation, bool filled, int c, int thickness) +{ + if ((x_axis > 0) && (y_axis > 0)) { + if ((x_axis == y_axis) || ((rotation % 180) == 0)) { + scratch_draw_sheared_ellipse(img, x, y, x_axis / 2, y_axis / 2, filled, 1, 0, c, thickness); + } else if ((rotation % 180) == 90) { + scratch_draw_sheared_ellipse(img, x, y, y_axis / 2, x_axis / 2, filled, 1, 0, c, thickness); + } else { + float theta = fast_atanf(IM_DIV(y_axis, x_axis) * (-tanf(IM_DEG2RAD(rotation)))); + float shear_dx = (x_axis * cosf(theta) * cosf(IM_DEG2RAD(rotation))) - (y_axis * sinf(theta) * sinf(IM_DEG2RAD(rotation))); + float shear_dy = (x_axis * cosf(theta) * sinf(IM_DEG2RAD(rotation))) + (y_axis * sinf(theta) * cosf(IM_DEG2RAD(rotation))); + float shear_x_axis = fast_fabsf(shear_dx); + float shear_y_axis = IM_DIV((y_axis * x_axis), shear_x_axis); + scratch_draw_sheared_ellipse(img, x, y, fast_floorf(shear_x_axis / 2), fast_floorf(shear_y_axis / 2), filled, shear_dx, shear_dy, c, thickness); + } + } +} + +void imlib_draw_ellipse(image_t *img, int cx, int cy, int rx, int ry, int rotation, int c, int thickness, bool fill) +{ + scratch_draw_rotated_ellipse(img, cx, cy, rx * 2, ry * 2, rotation, fill, c, thickness); +} + void imlib_draw_string(image_t *img, int x_off, int y_off, const char *str, int c, int scale, int x_spacing, int y_spacing, bool mono_space) { const int anchor = x_off; @@ -246,6 +344,63 @@ void imlib_draw_string(image_t *img, int x_off, int y_off, const char *str, int } } +static int safe_map_pixel(image_t *dst, image_t *src, int pixel) +{ + switch (dst->bpp) { + case IMAGE_BPP_BINARY: { + switch (src->bpp) { + case IMAGE_BPP_BINARY: { + return pixel; + } + case IMAGE_BPP_GRAYSCALE: { + return COLOR_GRAYSCALE_TO_BINARY(pixel); + } + case IMAGE_BPP_RGB565: { + return COLOR_RGB565_TO_BINARY(pixel); + } + default: { + return 0; + } + } + } + case IMAGE_BPP_GRAYSCALE: { + switch (src->bpp) { + case IMAGE_BPP_BINARY: { + return COLOR_BINARY_TO_GRAYSCALE(pixel); + } + case IMAGE_BPP_GRAYSCALE: { + return pixel; + } + case IMAGE_BPP_RGB565: { + return COLOR_RGB565_TO_GRAYSCALE(pixel); + } + default: { + return 0; + } + } + } + case IMAGE_BPP_RGB565: { + switch (src->bpp) { + case IMAGE_BPP_BINARY: { + return COLOR_BINARY_TO_RGB565(pixel); + } + case IMAGE_BPP_GRAYSCALE: { + return COLOR_GRAYSCALE_TO_RGB565(pixel); + } + case IMAGE_BPP_RGB565: { + return pixel; + } + default: { + return 0; + } + } + } + default: { + return 0; + } + } +} + void imlib_draw_image(image_t *img, image_t *other, int x_off, int y_off, float x_scale, float y_scale, image_t *mask) { float over_xscale = IM_DIV(1.0, x_scale), over_yscale = IM_DIV(1.0f, y_scale); @@ -256,8 +411,9 @@ void imlib_draw_image(image_t *img, image_t *other, int x_off, int y_off, float for (int x = 0, xx = fast_roundf(other->w * x_scale); x < xx; x++) { int other_x = fast_roundf(x * over_xscale); - if (mask && (!image_get_mask_pixel(mask, other_x, other_y))) continue; - imlib_set_pixel(img, x_off + x, y_off + y, imlib_get_pixel(other, other_x, other_y)); + if ((!mask) || image_get_mask_pixel(mask, other_x, other_y)) { + imlib_set_pixel(img, x_off + x, y_off + y, safe_map_pixel(img, other, imlib_get_pixel(other, other_x, other_y))); + } } } } diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index 164e867f8..5816dd343 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -1274,6 +1274,7 @@ void imlib_set_pixel(image_t *img, int x, int y, int p); void imlib_draw_line(image_t *img, int x0, int y0, int x1, int y1, int c, int thickness); void imlib_draw_rectangle(image_t *img, int rx, int ry, int rw, int rh, int c, int thickness, bool fill); void imlib_draw_circle(image_t *img, int cx, int cy, int r, int c, int thickness, bool fill); +void imlib_draw_ellipse(image_t *img, int cx, int cy, int rx, int ry, int rotation, int c, int thickness, bool fill); void imlib_draw_string(image_t *img, int x_off, int y_off, const char *str, int c, int scale, int x_spacing, int y_spacing, bool mono_space); void imlib_draw_image(image_t *img, image_t *other, int x_off, int y_off, float x_scale, float y_scale, image_t *mask); void imlib_flood_fill(image_t *img, int x, int y, diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index b2ce212b3..d6803e729 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -1309,6 +1309,31 @@ STATIC mp_obj_t py_image_draw_circle(uint n_args, const mp_obj_t *args, mp_map_t } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_circle_obj, 2, py_image_draw_circle); +STATIC mp_obj_t py_image_draw_ellipse(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 4, &arg_vec); + int arg_cx = mp_obj_get_int(arg_vec[0]); + int arg_cy = mp_obj_get_int(arg_vec[1]); + int arg_rx = mp_obj_get_int(arg_vec[2]); + int arg_ry = mp_obj_get_int(arg_vec[3]); + + int arg_rotation = + py_helper_keyword_int(n_args, args, offset + 0, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_rotation), 0); + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 1, kw_args, -1); // White. + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + bool arg_fill = + py_helper_keyword_int(n_args, args, offset + 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); + + imlib_draw_ellipse(arg_img, arg_cx, arg_cy, arg_rx, arg_ry, arg_rotation, arg_c, arg_thickness, arg_fill); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_ellipse_obj, 2, py_image_draw_ellipse); + STATIC mp_obj_t py_image_draw_string(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); @@ -1403,7 +1428,7 @@ STATIC mp_obj_t py_image_draw_image(uint n_args, const mp_obj_t *args, mp_map_t image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); image_t *arg_other = - py_helper_keyword_to_image_mutable_mask(n_args, args, 1, kw_args); + py_helper_arg_to_image_mutable(args[1]); const mp_obj_t *arg_vec; uint offset = py_helper_consume_array(n_args, args, 2, 2, &arg_vec); @@ -5269,6 +5294,7 @@ static const mp_rom_map_elem_t locals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR_draw_line), MP_ROM_PTR(&py_image_draw_line_obj)}, {MP_ROM_QSTR(MP_QSTR_draw_rectangle), MP_ROM_PTR(&py_image_draw_rectangle_obj)}, {MP_ROM_QSTR(MP_QSTR_draw_circle), MP_ROM_PTR(&py_image_draw_circle_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_ellipse), MP_ROM_PTR(&py_image_draw_ellipse_obj)}, {MP_ROM_QSTR(MP_QSTR_draw_string), MP_ROM_PTR(&py_image_draw_string_obj)}, {MP_ROM_QSTR(MP_QSTR_draw_cross), MP_ROM_PTR(&py_image_draw_cross_obj)}, {MP_ROM_QSTR(MP_QSTR_draw_arrow), MP_ROM_PTR(&py_image_draw_arrow_obj)}, diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index 4211fd1a0..7d7889361 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -398,6 +398,13 @@ Q(draw_circle) // duplicate Q(thickness) // duplicate Q(fill) +// Draw Ellipse +Q(draw_ellipse) +Q(rotation) +// duplicate Q(color) +// duplicate Q(thickness) +// duplicate Q(fill) + // Draw String Q(draw_string) // duplicate Q(color) @@ -800,7 +807,7 @@ Q(h) Q(pixels) Q(cx) Q(cy) -Q(rotation) +// duplicate Q(rotation) Q(code) Q(count) Q(area)