From fc74e70c3292027ad4a6f9bdc0015eb9ee1e5112 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Mon, 12 Mar 2018 01:14:03 -0400 Subject: [PATCH] Upgrade Drawing Features Heavily upgraded the drawing features onboard the OpenMV Cam. We now have all the basic drawing methods folks expect along with all the parameters you need. Finally! You can make big text fonts. * Added line thickness support * Added shape fills * Added text scaling * Added draw arrow. --- src/Makefile | 1 + src/omv/Makefile | 1 + src/omv/img/draw.c | 205 +++++++++++++++++++ src/omv/img/hog.c | 2 +- src/omv/img/imlib.c | 133 ------------ src/omv/img/imlib.h | 20 +- src/omv/py/py_helper.c | 12 +- src/omv/py/py_image.c | 102 +++++++-- src/omv/py/qstrdefsomv.h | 59 +++++- usr/examples/03-Drawing/arrow_drawing.py | 31 +++ usr/examples/03-Drawing/circle_drawing.py | 31 +++ usr/examples/03-Drawing/color_drawing.py | 55 ----- usr/examples/03-Drawing/crazy_drawing.py | 118 ----------- usr/examples/03-Drawing/cross_drawing.py | 29 +++ usr/examples/03-Drawing/line_drawing.py | 31 +++ usr/examples/03-Drawing/rectangle_drawing.py | 31 +++ usr/examples/03-Drawing/text_drawing.py | 29 +++ 17 files changed, 537 insertions(+), 353 deletions(-) create mode 100644 src/omv/img/draw.c create mode 100644 usr/examples/03-Drawing/arrow_drawing.py create mode 100644 usr/examples/03-Drawing/circle_drawing.py delete mode 100644 usr/examples/03-Drawing/color_drawing.py delete mode 100644 usr/examples/03-Drawing/crazy_drawing.py create mode 100644 usr/examples/03-Drawing/cross_drawing.py create mode 100644 usr/examples/03-Drawing/line_drawing.py create mode 100644 usr/examples/03-Drawing/rectangle_drawing.py create mode 100644 usr/examples/03-Drawing/text_drawing.py diff --git a/src/Makefile b/src/Makefile index 124f10628..6fd8c3e89 100755 --- a/src/Makefile +++ b/src/Makefile @@ -153,6 +153,7 @@ FIRM_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/, \ FIRM_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/img/,\ binary.o \ blob.o \ + draw.o \ qrcode.o \ apriltag.o \ dmtx.o \ diff --git a/src/omv/Makefile b/src/omv/Makefile index e8ce37784..4e12a86dd 100644 --- a/src/omv/Makefile +++ b/src/omv/Makefile @@ -24,6 +24,7 @@ SRCS += $(addprefix , \ SRCS += $(addprefix img/, \ binary.c \ blob.c \ + draw.c \ qrcode.c \ apriltag.c \ dmtx.c \ diff --git a/src/omv/img/draw.c b/src/omv/img/draw.c new file mode 100644 index 000000000..96b2782b2 --- /dev/null +++ b/src/omv/img/draw.c @@ -0,0 +1,205 @@ +/* This file is part of the OpenMV project. + * Copyright (c) 2013-2018 Ibrahim Abdelkader & Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + */ + +#include "font.h" +#include "imlib.h" + +// Get pixel (handles boundary check and image type check). +int imlib_get_pixel(image_t *img, int x, int y) +{ + if ((0 <= x) && (x < img->w) && (0 <= y) && (y < img->h)) { + switch(img->bpp) { + case IMAGE_BPP_BINARY: { + return IMAGE_GET_BINARY_PIXEL(img, x, y); + } + case IMAGE_BPP_GRAYSCALE: { + return IMAGE_GET_GRAYSCALE_PIXEL(img, x, y); + } + case IMAGE_BPP_RGB565: { + return IMAGE_GET_RGB565_PIXEL(img, x, y); + } + default: { + return -1; + } + } + } + + return -1; +} + +// Set pixel (handles boundary check and image type check). +void imlib_set_pixel(image_t *img, int x, int y, int p) +{ + if ((0 <= x) && (x < img->w) && (0 <= y) && (y < img->h)) { + switch(img->bpp) { + case IMAGE_BPP_BINARY: { + IMAGE_PUT_BINARY_PIXEL(img, x, y, p); + break; + } + case IMAGE_BPP_GRAYSCALE: { + IMAGE_PUT_GRAYSCALE_PIXEL(img, x, y, p); + break; + } + case IMAGE_BPP_RGB565: { + IMAGE_PUT_RGB565_PIXEL(img, x, y, p); + break; + } + default: { + break; + } + } + } +} + +// https://stackoverflow.com/questions/1201200/fast-algorithm-for-drawing-filled-circles +static void point_fill(image_t *img, int cx, int cy, int r, int c) +{ + for (int y = -r; y <= r; y++) { + for (int x = -r; x <= r; x++) { + if ((x * x) + (y * y) <= (r * r)) { + imlib_set_pixel(img, cx + x, cy + y, c); + } + } + } +} + +// https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C +void imlib_draw_line(image_t *img, int x0, int y0, int x1, int y1, int c, int thickness) +{ + int dx = abs(x1 - x0), sx = (x0 < x1) ? 1 : -1; + int dy = abs(y1 - y0), sy = (y0 < y1) ? 1 : -1; + int err = ((dx > dy) ? dx : -dy) / 2; + + for (;;) { + point_fill(img, x0, y0, thickness - 1, c); + if ((x0 == x1) && (y0 == y1)) break; + int e2 = err; + if (e2 > -dx) { err -= dy; x0 += sx; } + if (e2 < dy) { err += dx; y0 += sy; } + } +} + +static void xLine(image_t *img, int x1, int x2, int y, int c) +{ + while (x1 <= x2) imlib_set_pixel(img, x1++, y, c); +} + +static void yLine(image_t *img, int x, int y1, int y2, int c) +{ + while (y1 <= y2) imlib_set_pixel(img, x, y1++, c); +} + +void imlib_draw_rectangle(image_t *img, int rx, int ry, int rw, int rh, int c, int thickness, bool fill) +{ + if (fill) { + + for (int y = ry, yy = ry + rh; y < yy; y++) { + for (int x = rx, xx = rx + rw; x < xx; x++) { + imlib_set_pixel(img, x, y, c); + } + } + + } else if (thickness > 0) { + + for (int i = rx - ((thickness - 0) / 2), j = rx + rw + ((thickness - 1) / 2), + k = ry + rh - 1; i < j; i++) { + yLine(img, i, ry - ((thickness - 0) / 2), ry + ((thickness - 1) / 2), c); + yLine(img, i, k - ((thickness - 0) / 2), k + ((thickness - 1) / 2), c); + } + + for (int i = ry - ((thickness - 0) / 2), j = ry + rh + ((thickness - 1) / 2), + k = rx + rw - 1; i < j; i++) { + xLine(img, rx - ((thickness - 0) / 2), rx + ((thickness - 1) / 2), i, c); + xLine(img, k - ((thickness - 0) / 2), k + ((thickness - 1) / 2), i, c); + } + } +} + +// https://stackoverflow.com/questions/27755514/circle-with-thickness-drawing-algorithm +void imlib_draw_circle(image_t *img, int cx, int cy, int r, int c, int thickness, bool fill) +{ + if (fill) { + point_fill(img, cx, cy, r, c); + } else if (thickness > 0) { + thickness = IM_MIN(thickness, r); + + int xo = r + ((thickness - 0) / 2); + int xi = r - ((thickness - 1) / 2); + int y = 0; + int erro = 1 - xo; + int erri = 1 - xi; + + while(xo >= y) { + xLine(img, cx + xi, cx + xo, cy + y, c); + yLine(img, cx + y, cy + xi, cy + xo, c); + xLine(img, cx - xo, cx - xi, cy + y, c); + yLine(img, cx - y, cy + xi, cy + xo, c); + xLine(img, cx - xo, cx - xi, cy - y, c); + yLine(img, cx - y, cy - xo, cy - xi, c); + xLine(img, cx + xi, cx + xo, cy - y, c); + yLine(img, cx + y, cy - xo, cy - xi, c); + + y++; + + if (erro < 0) { + erro += 2 * y + 1; + } else { + xo--; + erro += 2 * (y - xo + 1); + } + + if (y > (r - ((thickness - 1) / 2))) { + xi = y; + } else { + if (erri < 0) { + erri += 2 * y + 1; + } else { + xi--; + erri += 2 * (y - xi + 1); + } + } + } + } +} + +void imlib_draw_string(image_t *img, int x_off, int y_off, const char *str, int c, int scale) +{ + const int anchor = x_off; + + for(char ch, last = '\0'; (ch = *str); str++, last = ch) { + + if ((last == '\r') && (ch == '\n')) { // handle "\r\n" strings + continue; + } + + if ((ch == '\n') || (ch == '\r')) { // handle '\n' or '\r' strings + x_off = anchor; + y_off += font[0].h * scale; // newline height == space height + continue; + } + + if ((ch < ' ') || (ch > '~')) { // handle unknown characters + imlib_draw_rectangle(img, + x_off + ((scale * 3) / 2), + y_off + ((scale * 3) / 2), + (font[0].w * scale) - (((scale * 3) / 2) * 2), + (font[0].h * scale) - (((scale * 3) / 2) * 2), + c, scale, false); + continue; + } + + const glyph_t *g = &font[ch - ' ']; + + for (int y = 0, yy = g->h * scale; y < yy; y++) { + for (int x = 0, xx = g->w * scale; x < xx; x++) { + if (g->data[y / scale] & (1 << (g->w - (x / scale)))) { + imlib_set_pixel(img, (x_off + x), (y_off + y), c); + } + } + } + + x_off += g->w * scale; + } +} diff --git a/src/omv/img/hog.c b/src/omv/img/hog.c index 125ee70b9..47eefbe6f 100644 --- a/src/omv/img/hog.c +++ b/src/omv/img/hog.c @@ -117,7 +117,7 @@ void imlib_find_hog(image_t *src, rectangle_t *roi, int cell_size) bin_t *bin = array_at(gds, i); int x2 = l * cos_table[bin->d]; int y2 = l * sin_table[bin->d]; - imlib_draw_line(src, (x1 - x2), (y1 + y2), (x1 + x2), (y1 - y2), bin->m); + imlib_draw_line(src, (x1 - x2), (y1 + y2), (x1 + x2), (y1 - y2), bin->m, 1); } hog_index += N_BINS; diff --git a/src/omv/img/imlib.c b/src/omv/img/imlib.c index 22a8bbe07..19703fcbe 100644 --- a/src/omv/img/imlib.c +++ b/src/omv/img/imlib.c @@ -519,139 +519,6 @@ void imlib_save_image(image_t *img, const char *path, rectangle_t *roi, int qual } } -void imlib_copy_image(image_t *dst, image_t *src, rectangle_t *roi) -{ - if (IM_IS_JPEG(src)) { - dst->w = src->w; - dst->h = src->h; - dst->bpp = src->bpp; - dst->pixels = xalloc(src->bpp); - memcpy(dst->pixels, src->pixels, src->bpp); - } else { - rectangle_t rect; - if (!rectangle_subimg(src, roi, &rect)) ff_no_intersection(NULL); - dst->w = rect.w; - dst->h = rect.h; - dst->bpp = src->bpp; - dst->pixels = xalloc(rect.w * rect.h * src->bpp); - uint8_t *dst_pointer = dst->pixels; - for (int i = rect.y; i < (rect.y + rect.h); i++) { - int length = rect.w * src->bpp; - memcpy(dst_pointer, - src->pixels + (rect.x * src->bpp) + (i * src->w * src->bpp), - length); - dst_pointer += length; - } - } -} - -//////////////////////////////////////////////////////////////////////////////// - -// Get pixel (handles boundary check and image type check). -int imlib_get_pixel(image_t *img, int x, int y) -{ - return (IM_X_INSIDE(img, x) && IM_Y_INSIDE(img, y)) ? - ( IM_IS_GS(img) - ? IM_GET_GS_PIXEL(img, x, y) - : IM_GET_RGB565_PIXEL(img, x, y) ) - : 0; -} - -// Set pixel (handles boundary check and image type check). -void imlib_set_pixel(image_t *img, int x, int y, int p) -{ - if (IM_X_INSIDE(img, x) && IM_Y_INSIDE(img, y)) { - if (IM_IS_GS(img)) { - IM_SET_GS_PIXEL(img, x, y, p); - } else { - IM_SET_RGB565_PIXEL(img, x, y, p); - } - } -} - -//////////////////////////////////////////////////////////////////////////////// - -void imlib_draw_line(image_t *img, int x0, int y0, int x1, int y1, int c) -{ - int dx = abs(x1-x0); - int dy = abs(y1-y0); - int sx = x0dy ? dx : -dy)/2; - for (;;) { - imlib_set_pixel(img, x0, y0, c); - if (x0==x1 && y0==y1) break; - int e2 = err; - if (e2 > -dx) { err -= dy; x0 += sx; } - if (e2 < dy) { err += dx; y0 += sy; } - } -} - -void imlib_draw_rectangle(image_t *img, int rx, int ry, int rw, int rh, int c) -{ - if (rw<=0 || rh<=0) { - return; - } - for (int i=rx, j=rx+rw, k=ry+rh-1; i=y) { - imlib_set_pixel(img, x + cx, y + cy, c); - imlib_set_pixel(img, y + cx, x + cy, c); - imlib_set_pixel(img, -x + cx, y + cy, c); - imlib_set_pixel(img, -y + cx, x + cy, c); - imlib_set_pixel(img, -x + cx, -y + cy, c); - imlib_set_pixel(img, -y + cx, -x + cy, c); - imlib_set_pixel(img, x + cx, -y + cy, c); - imlib_set_pixel(img, y + cx, -x + cy, c); - y++; - if (radiusError<0) { - radiusError += 2 * y + 1; - } else { - x--; - radiusError += 2 * (y - x + 1); - } - } -} - -void imlib_draw_string(image_t *img, int x_off, int y_off, const char *str, int c) -{ - const int anchor = x_off; - for(char ch, last='\0'; (ch=*str); str++, last=ch) { - if (last=='\r' && ch=='\n') { // handle "\r\n" strings - continue; - } - if (ch=='\n' || ch=='\r') { // handle '\n' or '\r' strings - x_off = anchor; - y_off += font[0].h; // newline height == space height - continue; - } - if (ch<' ' || ch>'~') { - imlib_draw_rectangle(img,(x_off+1),(y_off+1),font[0].w-2,font[0].h-2,c); - continue; - } - const glyph_t *g = &font[ch-' ']; - for (int y=0; yh; y++) { - for (int x=0; xw; x++) { - if (g->data[y] & (1<<(g->w-x))) { - imlib_set_pixel(img, (x_off+x), (y_off+y), c); - } - } - } - x_off += g->w; - } -} - //////////////////////////////////////////////////////////////////////////////// void imlib_histeq(image_t *img) diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index 48d2144d5..377068dd3 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -218,7 +218,7 @@ extern const uint8_t g826_table[256]; __typeof__ (r5) _r5 = (r5); \ __typeof__ (g6) _g6 = (g6); \ __typeof__ (b5) _b5 = (b5); \ - (_r5 << 3) | (_g6 >> 3) | (_g6 << 13) | (_b5 << 8); \ + (_r5 << 3) | (_g6 >> 3) | ((_g6 & 0x7) << 13) | (_b5 << 8); \ }) #define COLOR_R8_G8_B8_TO_RGB565(r8, g8, b8) COLOR_R5_G6_B5_TO_RGB565(COLOR_R8_TO_R5(r8), COLOR_G8_TO_G6(g8), COLOR_B8_TO_B5(b8)) @@ -1107,7 +1107,6 @@ bool imlib_read_geometry(FIL *fp, image_t *img, const char *path, img_read_setti void imlib_image_operation(image_t *img, const char *path, image_t *other, line_op_t op, void *data); void imlib_load_image(image_t *img, const char *path); void imlib_save_image(image_t *img, const char *path, rectangle_t *roi, int quality); -void imlib_copy_image(image_t *dst, image_t *src, rectangle_t *roi); /* GIF functions */ void gif_open(FIL *fp, int width, int height, bool color, bool loop); @@ -1119,10 +1118,6 @@ void mjpeg_open(FIL *fp, int width, int height); void mjpeg_add_frame(FIL *fp, uint32_t *frames, uint32_t *bytes, image_t *img, int quality); void mjpeg_close(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps); -/* Basic image functions */ -int imlib_get_pixel(image_t *img, int x, int y); -void imlib_set_pixel(image_t *img, int x, int y, int p); - /* Point functions */ point_t *point_alloc(int16_t x, int16_t y); bool point_equal(point_t *p1, point_t *p2); @@ -1136,12 +1131,6 @@ bool rectangle_subimg(image_t *img, rectangle_t *r, rectangle_t *r_out); array_t *rectangle_merge(array_t *rectangles); void rectangle_expand(rectangle_t *r, int x, int y); -/* Drawing functions */ -void imlib_draw_line(image_t *img, int x0, int y0, int x1, int y1, int c); -void imlib_draw_rectangle(image_t *img, int rx, int ry, int rw, int rh, int c); -void imlib_draw_circle(image_t *img, int cx, int cy, int r, int c); -void imlib_draw_string(image_t *img, int x_off, int y_off, const char *str, int c); - /* Image Morphing */ void imlib_morph(image_t *img, const int ksize, const int8_t *krn, const float m, const int b); @@ -1226,6 +1215,13 @@ void imlib_edge_canny(image_t *src, rectangle_t *roi, int low_thresh, int high_t // HoG void imlib_find_hog(image_t *src, rectangle_t *roi, int cell_size); +// Drawing Functions +int imlib_get_pixel(image_t *img, int x, int y); +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_string(image_t *img, int x_off, int y_off, const char *str, int c, int scale); // Binary Functions void imlib_binary(image_t *img, list_t *thresholds, bool invert, bool zero); void imlib_invert(image_t *img); diff --git a/src/omv/py/py_helper.c b/src/omv/py/py_helper.c index ecf421624..0023d3700 100644 --- a/src/omv/py/py_helper.c +++ b/src/omv/py/py_helper.c @@ -181,9 +181,9 @@ int py_helper_keyword_color(image_t *img, uint n_args, const mp_obj_t *args, uin } else { mp_obj_t *arg_color; mp_obj_get_array_fixed_n(kw_arg->value, 3, &arg_color); - default_val = COLOR_R5_G6_B5_TO_RGB565(COLOR_R8_TO_R5(mp_obj_get_int(arg_color[0])), - COLOR_G8_TO_G6(mp_obj_get_int(arg_color[1])), - COLOR_B8_TO_B5(mp_obj_get_int(arg_color[2]))); + default_val = COLOR_R8_G8_B8_TO_RGB565(IM_MAX(IM_MIN(mp_obj_get_int(arg_color[0]), COLOR_R8_MAX), COLOR_R8_MIN), + IM_MAX(IM_MIN(mp_obj_get_int(arg_color[1]), COLOR_G8_MAX), COLOR_G8_MIN), + IM_MAX(IM_MIN(mp_obj_get_int(arg_color[2]), COLOR_B8_MAX), COLOR_B8_MIN)); switch(img->bpp) { case IMAGE_BPP_BINARY: { default_val = COLOR_RGB565_TO_BINARY(default_val); @@ -204,9 +204,9 @@ int py_helper_keyword_color(image_t *img, uint n_args, const mp_obj_t *args, uin } else { mp_obj_t *arg_color; mp_obj_get_array_fixed_n(args[arg_index], 3, &arg_color); - default_val = COLOR_R5_G6_B5_TO_RGB565(COLOR_R8_TO_R5(mp_obj_get_int(arg_color[0])), - COLOR_G8_TO_G6(mp_obj_get_int(arg_color[1])), - COLOR_B8_TO_B5(mp_obj_get_int(arg_color[2]))); + default_val = COLOR_R8_G8_B8_TO_RGB565(IM_MAX(IM_MIN(mp_obj_get_int(arg_color[0]), COLOR_R8_MAX), COLOR_R8_MIN), + IM_MAX(IM_MIN(mp_obj_get_int(arg_color[1]), COLOR_G8_MAX), COLOR_G8_MIN), + IM_MAX(IM_MIN(mp_obj_get_int(arg_color[2]), COLOR_B8_MAX), COLOR_B8_MIN)); switch(img->bpp) { case IMAGE_BPP_BINARY: { default_val = COLOR_RGB565_TO_BINARY(default_val); diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index ac2ae13ed..3a2a6c92e 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -106,6 +106,12 @@ static const mp_obj_type_t py_kp_type = { .unary_op = py_kp_unary_op, }; +py_kp_obj_t *py_kpts_obj(mp_obj_t kpts_obj) +{ + PY_ASSERT_TYPE(kpts_obj, &py_kp_type); + return kpts_obj; +} + // LBP descriptor ///////////////////////////////////////////////////////////// typedef struct _py_lbp_obj_t { @@ -1012,9 +1018,11 @@ STATIC mp_obj_t py_image_draw_line(uint n_args, const mp_obj_t *args, mp_map_t * int arg_y1 = mp_obj_get_int(arg_vec[3]); int arg_c = - py_helper_keyword_color(arg_img, n_args, args, offset, kw_args, -1); // White. + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); - imlib_draw_line(arg_img, arg_x0, arg_y0, arg_x1, arg_y1, arg_c); + imlib_draw_line(arg_img, arg_x0, arg_y0, arg_x1, arg_y1, arg_c, arg_thickness); return args[0]; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_line_obj, 2, py_image_draw_line); @@ -1031,9 +1039,13 @@ STATIC mp_obj_t py_image_draw_rectangle(uint n_args, const mp_obj_t *args, mp_ma int arg_rh = mp_obj_get_int(arg_vec[3]); int arg_c = - py_helper_keyword_color(arg_img, n_args, args, offset, kw_args, -1); // White. + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + bool arg_fill = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); - imlib_draw_rectangle(arg_img, arg_rx, arg_ry, arg_rw, arg_rh, arg_c); + imlib_draw_rectangle(arg_img, arg_rx, arg_ry, arg_rw, arg_rh, arg_c, arg_thickness, arg_fill); return args[0]; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_rectangle_obj, 2, py_image_draw_rectangle); @@ -1049,9 +1061,13 @@ STATIC mp_obj_t py_image_draw_circle(uint n_args, const mp_obj_t *args, mp_map_t int arg_cr = mp_obj_get_int(arg_vec[2]); int arg_c = - py_helper_keyword_color(arg_img, n_args, args, offset, kw_args, -1); // White. + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + bool arg_fill = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); - imlib_draw_circle(arg_img, arg_cx, arg_cy, arg_cr, arg_c); + imlib_draw_circle(arg_img, arg_cx, arg_cy, arg_cr, arg_c, arg_thickness, arg_fill); return args[0]; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_circle_obj, 4, py_image_draw_circle); @@ -1067,9 +1083,11 @@ STATIC mp_obj_t py_image_draw_string(uint n_args, const mp_obj_t *args, mp_map_t const char *arg_str = mp_obj_str_get_str(arg_vec[2]); int arg_c = - py_helper_keyword_color(arg_img, n_args, args, offset, kw_args, -1); // White. + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_scale = + py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_scale), 1); - imlib_draw_string(arg_img, arg_x_off, arg_y_off, arg_str, arg_c); + imlib_draw_string(arg_img, arg_x_off, arg_y_off, arg_str, arg_c, arg_scale); return args[0]; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_string_obj, 4, py_image_draw_string); @@ -1084,36 +1102,83 @@ STATIC mp_obj_t py_image_draw_cross(uint n_args, const mp_obj_t *args, mp_map_t int arg_y = mp_obj_get_int(arg_vec[1]); int arg_c = - py_helper_keyword_color(arg_img, n_args, args, offset, kw_args, -1); // White. + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. int arg_s = py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 5); + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); - imlib_draw_line(arg_img, arg_x-arg_s, arg_y , arg_x+arg_s, arg_y , arg_c); - imlib_draw_line(arg_img, arg_x , arg_y-arg_s, arg_x , arg_y+arg_s, arg_c); + imlib_draw_line(arg_img, arg_x - arg_s, arg_y , arg_x + arg_s, arg_y , arg_c, arg_thickness); + imlib_draw_line(arg_img, arg_x , arg_y - arg_s, arg_x , arg_y + arg_s, arg_c, arg_thickness); return args[0]; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_cross_obj, 2, py_image_draw_cross); +STATIC mp_obj_t py_image_draw_arrow(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_x0 = mp_obj_get_int(arg_vec[0]); + int arg_y0 = mp_obj_get_int(arg_vec[1]); + int arg_x1 = mp_obj_get_int(arg_vec[2]); + int arg_y1 = mp_obj_get_int(arg_vec[3]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_s = + py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 10); + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + + // https://stackoverflow.com/questions/4165881/c-draw-a-line-in-gdi-with-a-filled-arrow-head-at-the-end + + int dx = (arg_x1 - arg_x0); + int dy = (arg_y1 - arg_y0); + + float length = fast_sqrtf((dx * dx) + (dy * dy)); + + float ux = dx / length; + float uy = dy / length; + float vx = -uy; + float vy = ux; + + int a0x = arg_x1 - (arg_s * ux) + (arg_s * vx * 0.5); + int a0y = arg_y1 - (arg_s * uy) + (arg_s * vy * 0.5); + int a1x = arg_x1 - (arg_s * ux) - (arg_s * vx * 0.5); + int a1y = arg_y1 - (arg_s * uy) - (arg_s * vy * 0.5); + + imlib_draw_line(arg_img, arg_x0, arg_y0, arg_x1, arg_y1, arg_c, arg_thickness); + imlib_draw_line(arg_img, arg_x1, arg_y1, a0x, a0y, arg_c, arg_thickness); + imlib_draw_line(arg_img, arg_x1, arg_y1, a1x, a1y, arg_c, arg_thickness); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_arrow_obj, 2, py_image_draw_arrow); + STATIC mp_obj_t py_image_draw_keypoints(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]); - py_kp_obj_t *kpts_obj = (py_kp_obj_t*) args[1]; - PY_ASSERT_TYPE(kpts_obj, &py_kp_type); + py_kp_obj_t *kpts_obj = py_kpts_obj(args[1]); int arg_c = py_helper_keyword_color(arg_img, n_args, args, 2, kw_args, -1); // White. int arg_s = - py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), arg_img->w * 0.1f); + py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), arg_img->w / 10); + int arg_thickness = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + bool arg_fill = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); - for (int i=0; ikpts); i++) { + for (int i = 0, ii = array_length(kpts_obj->kpts); i < ii; i++) { kp_t *kp = array_at(kpts_obj->kpts, i); int cx = kp->x; int cy = kp->y; - int size = arg_s/2; + int size = arg_s / 2; int si = sin_table[kp->angle] * size; int co = cos_table[kp->angle] * size; - imlib_draw_line(arg_img, cx, cy, cx+co, cy+si, arg_c); - imlib_draw_circle(arg_img, cx, cy, size, arg_c); + imlib_draw_line(arg_img, cx, cy, cx + co, cy + si, arg_c, arg_thickness); + imlib_draw_circle(arg_img, cx, cy, size, arg_c, arg_thickness, arg_fill); } return args[0]; @@ -4572,6 +4637,7 @@ static const mp_rom_map_elem_t locals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR_draw_circle), MP_ROM_PTR(&py_image_draw_circle_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)}, {MP_ROM_QSTR(MP_QSTR_draw_keypoints), MP_ROM_PTR(&py_image_draw_keypoints_obj)}, /* Binary Methods */ {MP_ROM_QSTR(MP_QSTR_binary), MP_ROM_PTR(&py_image_binary_obj)}, diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index e57e80b8c..7a8a8a5f1 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -38,15 +38,6 @@ Q(width) Q(height) Q(format) Q(size) -Q(get_pixel) -Q(rgbtuple) -Q(set_pixel) -Q(draw_line) -Q(draw_rectangle) -Q(draw_circle) -Q(draw_string) -Q(draw_cross) -Q(draw_keypoints) Q(morph) Q(midpoint) Q(mean) @@ -70,7 +61,6 @@ Q(find_edges) Q(find_hog) Q(cmp_lbp) Q(quality) -Q(color) Q(roi) Q(offset) Q(threshold) @@ -330,6 +320,55 @@ Q(CPUFREQ_216MHZ) Q(get_frequency) Q(set_frequency) +// Get Pixel +Q(get_pixel) +Q(rgbtuple) + +// Set Pixel +Q(set_pixel) +Q(color) + +// Draw Line +Q(draw_line) +// duplicate Q(color) +Q(thickness) + +// Draw Rectangle +Q(draw_rectangle) +// duplicate Q(color) +// duplicate Q(thickness) +Q(fill) + +// Draw Circle +Q(draw_circle) +// duplicate Q(color) +// duplicate Q(thickness) +// duplicate Q(fill) + +// Draw String +Q(draw_string) +// duplicate Q(color) +// duplicate Q(scale) + +// Draw Cross +Q(draw_cross) +// duplicate Q(color) +// duplicate Q(size) +// duplicate Q(thickness) + +// Draw Arrow +Q(draw_arrow) +// duplicate Q(color) +// duplicate Q(size) +// duplicate Q(thickness) + +// Draw Keypoints +Q(draw_keypoints) +// duplicate Q(color) +// duplicate Q(size) +// duplicate Q(thickness) +// duplicate Q(fill) + // Binary Q(binary) Q(invert) diff --git a/usr/examples/03-Drawing/arrow_drawing.py b/usr/examples/03-Drawing/arrow_drawing.py new file mode 100644 index 000000000..33b190280 --- /dev/null +++ b/usr/examples/03-Drawing/arrow_drawing.py @@ -0,0 +1,31 @@ +# Arrow Drawing +# +# This example shows off drawing arrows 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): + x0 = (pyb.rng() % (2*img.width())) - (img.width()//2) + y0 = (pyb.rng() % (2*img.height())) - (img.height()//2) + x1 = (pyb.rng() % (2*img.width())) - (img.width()//2) + y1 = (pyb.rng() % (2*img.height())) - (img.height()//2) + 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 x0, y0, x1, and y1. Otherwise, it expects a (x0,y0,x1,y1) tuple. + img.draw_arrow(x0, y0, x1, y1, color = (r, g, b), size = 30, thickness = 2) + + print(clock.fps()) diff --git a/usr/examples/03-Drawing/circle_drawing.py b/usr/examples/03-Drawing/circle_drawing.py new file mode 100644 index 000000000..373d5a40f --- /dev/null +++ b/usr/examples/03-Drawing/circle_drawing.py @@ -0,0 +1,31 @@ +# Circle Drawing +# +# This example shows off drawing circles 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 = pyb.rng() % (max(img.height(), img.width())//2) + + 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, and radius. Otherwise, it expects a (x,y,radius) tuple. + img.draw_circle(x, y, radius, color = (r, g, b), thickness = 2, fill = False) + + print(clock.fps()) diff --git a/usr/examples/03-Drawing/color_drawing.py b/usr/examples/03-Drawing/color_drawing.py deleted file mode 100644 index 209287e9d..000000000 --- a/usr/examples/03-Drawing/color_drawing.py +++ /dev/null @@ -1,55 +0,0 @@ -# Color Drawing Example -# -# This example shows off your OpenMV Cam's built-in drawing capabilities. This -# example was originally a test but serves as good reference code. Please put -# your IDE into non-JPEG mode to see the best drawing quality. - -import sensor, image, time - -sensor.reset() -sensor.set_framesize(sensor.QVGA) - -# All drawing functions use the same code to pass color. -# So we just need to test one function. - -while(True): - - # Test Draw Line (GRAYSCALE) - sensor.set_pixformat(sensor.GRAYSCALE) - for i in range(10): - img = sensor.snapshot() - for i in range(img.width()): - c = ((i * 255) + (img.width()/2)) / img.width() - img.draw_line([i, 0, i, img.height()-1], color = int(c)) - sensor.snapshot() - time.sleep(1000) - - # Test Draw Line (RGB565) - sensor.set_pixformat(sensor.RGB565) - for i in range(10): - img = sensor.snapshot() - for i in range(img.width()): - c = ((i * 255) + (img.width()/2)) / img.width() - img.draw_line([i, 0, i, img.height()-1], color = [int(c), 0, 0]) - sensor.snapshot() - time.sleep(1000) - - # Test Draw Line (RGB565) - sensor.set_pixformat(sensor.RGB565) - for i in range(10): - img = sensor.snapshot() - for i in range(img.width()): - c = ((i * 255) + (img.width()/2)) / img.width() - img.draw_line([i, 0, i, img.height()-1], color = [0, int(c), 0]) - sensor.snapshot() - time.sleep(1000) - - # Test Draw Line (RGB565) - sensor.set_pixformat(sensor.RGB565) - for i in range(10): - img = sensor.snapshot() - for i in range(img.width()): - c = ((i * 255) + (img.width()/2)) / img.width() - img.draw_line([i, 0, i, img.height()-1], color = [0, 0, int(c)]) - sensor.snapshot() - time.sleep(1000) diff --git a/usr/examples/03-Drawing/crazy_drawing.py b/usr/examples/03-Drawing/crazy_drawing.py deleted file mode 100644 index 8db1beb09..000000000 --- a/usr/examples/03-Drawing/crazy_drawing.py +++ /dev/null @@ -1,118 +0,0 @@ -# Crazy Drawing Example -# -# This example shows off your OpenMV Cam's built-in drawing capabilities. This -# example was originally a test but serves as good reference code. Please put -# your IDE into non-JPEG mode to see the best drawing quality. - -import pyb, sensor, image, math - -sensor.reset() -sensor.set_framesize(sensor.QVGA) - -while(True): - - # Test Set Pixel - sensor.set_pixformat(sensor.GRAYSCALE) - for i in range(10): - img = sensor.snapshot() - for j in range(100): - x = (pyb.rng() % (2*img.width())) - (img.width()//2) - y = (pyb.rng() % (2*img.height())) - (img.height()//2) - img.set_pixel(x, y, 255) - sensor.set_pixformat(sensor.RGB565) - for i in range(10): - img = sensor.snapshot() - for j in range(100): - x = (pyb.rng() % (2*img.width())) - (img.width()//2) - y = (pyb.rng() % (2*img.height())) - (img.height()//2) - img.set_pixel(x, y, (255, 255, 255)) - - # Test Draw Line - sensor.set_pixformat(sensor.GRAYSCALE) - for i in range(10): - img = sensor.snapshot() - for j in range(100): - x0 = (pyb.rng() % (2*img.width())) - (img.width()//2) - y0 = (pyb.rng() % (2*img.height())) - (img.height()//2) - x1 = (pyb.rng() % (2*img.width())) - (img.width()//2) - y1 = (pyb.rng() % (2*img.height())) - (img.height()//2) - img.draw_line([x0, y0, x1, y1]) - sensor.set_pixformat(sensor.RGB565) - for i in range(10): - img = sensor.snapshot() - for j in range(100): - x0 = (pyb.rng() % (2*img.width())) - (img.width()//2) - y0 = (pyb.rng() % (2*img.height())) - (img.height()//2) - x1 = (pyb.rng() % (2*img.width())) - (img.width()//2) - y1 = (pyb.rng() % (2*img.height())) - (img.height()//2) - img.draw_line([x0, y0, x1, y1]) - - # Test Draw Rectangle - sensor.set_pixformat(sensor.GRAYSCALE) - for i in range(10): - img = sensor.snapshot() - for j in range(100): - x = (pyb.rng() % (2*img.width())) - (img.width()//2) - y = (pyb.rng() % (2*img.height())) - (img.height()//2) - w = (pyb.rng() % img.width()) - h = (pyb.rng() % img.height()) - img.draw_rectangle([x, y, w, h]) - sensor.set_pixformat(sensor.RGB565) - for i in range(10): - img = sensor.snapshot() - for j in range(100): - x = (pyb.rng() % (2*img.width())) - (img.width()//2) - y = (pyb.rng() % (2*img.height())) - (img.height()//2) - w = (pyb.rng() % img.width()) - h = (pyb.rng() % img.height()) - img.draw_rectangle([x, y, w, h]) - - # Test Draw Circle - sensor.set_pixformat(sensor.GRAYSCALE) - for i in range(10): - img = sensor.snapshot() - for j in range(100): - x = (pyb.rng() % (2*img.width())) - (img.width()//2) - y = (pyb.rng() % (2*img.height())) - (img.height()//2) - r = (pyb.rng() % (img.width() if (img.width() > img.height()) else img.height())) - img.draw_circle(x, y, r) - sensor.set_pixformat(sensor.RGB565) - for i in range(10): - img = sensor.snapshot() - for j in range(100): - x = (pyb.rng() % (2*img.width())) - (img.width()//2) - y = (pyb.rng() % (2*img.height())) - (img.height()//2) - r = (pyb.rng() % (img.width() if (img.width() > img.height()) else img.height())) - img.draw_circle(x, y, r) - - # Test Draw String - sensor.set_pixformat(sensor.GRAYSCALE) - for i in range(10): - img = sensor.snapshot() - for j in range(100): - x = (pyb.rng() % (2*img.width())) - (img.width()//2) - y = (pyb.rng() % (2*img.height())) - (img.height()//2) - img.draw_string(x, y, "Hello\nWorld!") - sensor.set_pixformat(sensor.RGB565) - for i in range(10): - img = sensor.snapshot() - for j in range(100): - x = (pyb.rng() % (2*img.width())) - (img.width()//2) - y = (pyb.rng() % (2*img.height())) - (img.height()//2) - img.draw_string(x, y, "Hello\nWorld!") - - # Test Draw Cross - sensor.set_pixformat(sensor.GRAYSCALE) - for i in range(10): - img = sensor.snapshot() - for j in range(100): - x = (pyb.rng() % (2*img.width())) - (img.width()//2) - y = (pyb.rng() % (2*img.height())) - (img.height()//2) - img.draw_cross(x, y) - sensor.set_pixformat(sensor.RGB565) - for i in range(10): - img = sensor.snapshot() - for j in range(100): - x = (pyb.rng() % (2*img.width())) - (img.width()//2) - y = (pyb.rng() % (2*img.height())) - (img.height()//2) - img.draw_cross(x, y) diff --git a/usr/examples/03-Drawing/cross_drawing.py b/usr/examples/03-Drawing/cross_drawing.py new file mode 100644 index 000000000..8d7e1e5ff --- /dev/null +++ b/usr/examples/03-Drawing/cross_drawing.py @@ -0,0 +1,29 @@ +# Cross Drawing +# +# This example shows off drawing crosses 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) + 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 and y. Otherwise, it expects a (x,y) tuple. + img.draw_cross(x, y, color = (r, g, b), size = 10, thickness = 2) + + print(clock.fps()) diff --git a/usr/examples/03-Drawing/line_drawing.py b/usr/examples/03-Drawing/line_drawing.py new file mode 100644 index 000000000..eb2d761bd --- /dev/null +++ b/usr/examples/03-Drawing/line_drawing.py @@ -0,0 +1,31 @@ +# Line Drawing +# +# This example shows off drawing lines 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): + x0 = (pyb.rng() % (2*img.width())) - (img.width()//2) + y0 = (pyb.rng() % (2*img.height())) - (img.height()//2) + x1 = (pyb.rng() % (2*img.width())) - (img.width()//2) + y1 = (pyb.rng() % (2*img.height())) - (img.height()//2) + 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 x0, y0, x1, and y1. Otherwise, it expects a (x0,y0,x1,y1) tuple. + img.draw_line(x0, y0, x1, y1, color = (r, g, b), thickness = 2) + + print(clock.fps()) diff --git a/usr/examples/03-Drawing/rectangle_drawing.py b/usr/examples/03-Drawing/rectangle_drawing.py new file mode 100644 index 000000000..ab2afac66 --- /dev/null +++ b/usr/examples/03-Drawing/rectangle_drawing.py @@ -0,0 +1,31 @@ +# Rectangle Drawing +# +# This example shows off drawing rectangles 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) + w = (pyb.rng() % (img.width()//2)) + h = (pyb.rng() % (img.height()//2)) + 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, w, and h. Otherwise, it expects a (x,y,w,h) tuple. + img.draw_rectangle(x, y, w, h, color = (r, g, b), thickness = 2, fill = False) + + print(clock.fps()) diff --git a/usr/examples/03-Drawing/text_drawing.py b/usr/examples/03-Drawing/text_drawing.py new file mode 100644 index 000000000..923607631 --- /dev/null +++ b/usr/examples/03-Drawing/text_drawing.py @@ -0,0 +1,29 @@ +# Text Drawing +# +# This example shows off drawing text 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) + 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, and text. Otherwise, it expects a (x,y,text) tuple. + img.draw_string(x, y, "Hello World!", color = (r, g, b), scale = 2) + + print(clock.fps())