diff --git a/scripts/examples/03-Drawing/image_drawing_advanced.py b/scripts/examples/03-Drawing/image_drawing_advanced.py new file mode 100644 index 000000000..bb7815c79 --- /dev/null +++ b/scripts/examples/03-Drawing/image_drawing_advanced.py @@ -0,0 +1,95 @@ +# Draw Image Testing script with bounce +# +# Exercise draw image with many different values for testing + +import sensor, image, time, pyb + +sensor.reset() +sensor.set_pixformat(sensor.RGB565) # or GRAYSCALE... +sensor.set_framesize(sensor.QQVGA) # or QQVGA... +sensor.skip_frames(time = 2000) +clock = time.clock() + +BOUNCE = True +RESCALE = True + +SMALL_IMAGE_SCALE = 3 + +CYCLE_FORMATS = True +CYCLE_MASK = True + +# Used when CYCLE_FORMATS or CYCLE_MASK is true +value_mixer = 0 + +# Location of small image +x=100 +y=50 + +# Bounce direction +xd=.1 +yd=.1 + +# Small image scaling +rescale = 1.0 +rd=0.01 +max_rescale = 5 +min_rescale = -max_rescale + +# Boundary to bounce within +xmin = -sensor.width() / SMALL_IMAGE_SCALE - 8 +ymin = -sensor.height() / SMALL_IMAGE_SCALE - 8 +xmax = sensor.width() + 8 +ymax = sensor.height() + 8 + +while(True): + clock.tick() + + status = "" + value_mixer = value_mixer + 1 + + img = sensor.snapshot() + # Makes a scaled copy of the sensor + small_img = img.mean_pooled(SMALL_IMAGE_SCALE, SMALL_IMAGE_SCALE) + + status = 'rgb565 ' + if CYCLE_FORMATS: + image_format = (value_mixer >> 8) & 3 + # To test combining different formats + if (image_format==1): small_img = small_img.to_bitmap(copy=True); status = 'bitmap ' + if (image_format==2): small_img = small_img.to_grayscale(copy=True); status = 'grayscale ' + if (image_format==3): small_img = small_img.to_rgb565(copy=True); status = 'rgb565 ' + + # update small image location + if BOUNCE: + x = x + xd + if (xxmax): + xd = -xd + + y = y + yd + if (yymax): + yd = -yd + + # Update small image scale + if RESCALE: + rescale = rescale + rd + if (rescalemax_rescale): + rd = -rd + + # Find the center of the image + scaled_width = int(small_img.width() * abs(rescale)) + scaled_height= int(small_img.height() * abs(rescale)) + draw_x = int(x - (scaled_width >> 1)) + draw_y = int(y - (scaled_height >> 1)) + + apply_mask = CYCLE_MASK and ((value_mixer >> 9) & 1) + if apply_mask: + img.draw_image(small_img, draw_x, draw_y, mask=small_img.to_bitmap(copy=True), x_scale=-rescale, y_scale=rescale, alpha=240) + status += 'alpha:240 ' + status += '+mask ' + else: + img.draw_image(small_img, draw_x, draw_y, x_scale=-rescale, y_scale=rescale, alpha=128) + status += 'alpha:128 ' + + img.draw_string(8, 0, status, mono_space = False) + + print(clock.fps()) diff --git a/src/omv/img/draw.c b/src/omv/img/draw.c index 16bcc9921..bbae94b36 100644 --- a/src/omv/img/draw.c +++ b/src/omv/img/draw.c @@ -11,29 +11,43 @@ #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; - } +void* imlib_compute_row_ptr(image_t *img, int y) { + switch(img->bpp) { + case IMAGE_BPP_BINARY: { + return IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + } + case IMAGE_BPP_GRAYSCALE: { + return IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + } + case IMAGE_BPP_RGB565: { + return IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + } + default: { + // This shouldn't happen, at least we return a valid memory block + return img->data; } } - - return -1; } +inline int imlib_get_pixel_fast(int img_bpp, void *row_ptr, int x) +{ + switch(img_bpp) { + case IMAGE_BPP_BINARY: { + return IMAGE_GET_BINARY_PIXEL_FAST((uint32_t*)row_ptr, x); + } + case IMAGE_BPP_GRAYSCALE: { + return IMAGE_GET_GRAYSCALE_PIXEL_FAST((uint8_t*)row_ptr, x); + } + case IMAGE_BPP_RGB565: { + return IMAGE_GET_RGB565_PIXEL_FAST((uint16_t*)row_ptr, x); + } + default: { + return -1; + } + } +} + + // Set pixel (handles boundary check and image type check). void imlib_set_pixel(image_t *img, int x, int y, int p) { @@ -474,55 +488,177 @@ static int safe_map_pixel(int dst_bpp, int src_bpp, int pixel) } } -void imlib_draw_image(image_t *img, image_t *other, int x_off, int y_off, float x_scale, float y_scale, float alpha, image_t *mask, const uint16_t *color_palette) +void imlib_draw_image(image_t *img, image_t *other, int x_off, int y_off, float x_scale, float y_scale, int alpha, image_t *mask, const uint16_t *color_palette) { - float over_xscale = IM_DIV(1.0, x_scale), over_yscale = IM_DIV(1.0f, y_scale); + // Scaler to convert from img scale to other scale + const float over_xscale = IM_DIV(1.0f, x_scale), over_yscale = IM_DIV(1.0f, y_scale); - const float neg_alpha = 1.0f - alpha; const int img_bpp = img->bpp; const int other_bpp = other->bpp; - const int xx = fast_floorf(other->w * x_scale); - const int yy = fast_floorf(other->h * y_scale); - for (int y = 0; y < yy; y++) { - int other_y = fast_floorf(y * over_yscale); + // Left or top of other is out of bounds + int other_x_start = (x_off < 0) ? -x_off : 0; + int other_y_start = (y_off < 0) ? -y_off : 0; + + // Scaled other size + int other_width_scaled = fast_floorf(abs(other->w * x_scale)); + int other_height_scaled = fast_floorf(abs(other->h * y_scale)); - for (int x = 0; x < xx; x++) { - int other_x = fast_floorf(x * over_xscale); + // Right or bottom of image is out of bounds + int other_x_end = (x_off + other_width_scaled >= img->w) ? img->w - x_off : other_width_scaled; + int other_y_end = (y_off + other_height_scaled >= img->h) ? img->h - y_off : other_height_scaled; - if ((!mask) || image_get_mask_pixel(mask, other_x, other_y)) { + // Check bounds are within img + if (other_x_start + x_off >= img->w || other_y_start + y_off >= img->h) return; + if (other_x_end + x_off <= 0 || other_y_end + y_off <= 0) return; - int other_pixel = imlib_get_pixel(other, other_x, other_y); - other_pixel = color_palette - ? safe_map_pixel(img_bpp, IMAGE_BPP_RGB565, color_palette[other_pixel]) - : safe_map_pixel(img_bpp, other_bpp, other_pixel); - int img_pixel = imlib_get_pixel(img, x_off + x, y_off + y); - - int result_pixel; - switch (img_bpp) { - case IMAGE_BPP_BINARY: { - result_pixel = (other_pixel*alpha + img_pixel*neg_alpha)>=0.5?1:0; - break; - } - case IMAGE_BPP_GRAYSCALE: { - result_pixel = other_pixel*alpha + img_pixel*neg_alpha; - break; - } - case IMAGE_BPP_RGB565: { - int r = COLOR_RGB565_TO_R5(other_pixel)*alpha + COLOR_RGB565_TO_R5(img_pixel)*neg_alpha; - int g = COLOR_RGB565_TO_G6(other_pixel)*alpha + COLOR_RGB565_TO_G6(img_pixel)*neg_alpha; - int b = COLOR_RGB565_TO_B5(other_pixel)*alpha + COLOR_RGB565_TO_B5(img_pixel)*neg_alpha; - result_pixel = COLOR_R5_G6_B5_TO_RGB565(r, g, b); - break; - } - default: { - result_pixel = 0; - break; + // If scaling is negative we essentially flip the other coordinates so they work from bottom right instead of top left + if (over_xscale < 0) { + other_width_scaled--; + other_x_start -= other_width_scaled; + other_x_end -= other_width_scaled; + x_off += other_width_scaled; + } + + if (over_yscale < 0) { + other_height_scaled--; + other_y_start -= other_height_scaled; + other_y_end -= other_height_scaled; + y_off += other_height_scaled; + } + + switch(img_bpp) { + case IMAGE_BPP_BINARY: { + // If alpha is less that 128 on a bitmap we're just copying the image back to the image, so do nothing + if (alpha >= 128) { + // Iterate the img area to be updated + for (int y = other_y_start; y < other_y_end; y++) { + uint32_t *img_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y_off + y); + + const int other_y = fast_floorf(y * over_yscale); + void *other_row_ptr = imlib_compute_row_ptr(other, other_y); + + for (int x = other_x_start; x < other_x_end; x++) { + const int other_x = fast_floorf(x * over_xscale); + + if ((!mask) || image_get_mask_pixel(mask, other_x, other_y)) { + uint32_t result_pixel = safe_map_pixel(IMAGE_BPP_BINARY, other_bpp, imlib_get_pixel_fast(other_bpp, other_row_ptr, other_x)); + IMAGE_PUT_BINARY_PIXEL_FAST(img_row_ptr, x_off + x, result_pixel); + } } } - - imlib_set_pixel(img, x_off + x, y_off + y, result_pixel); } + break; + } + case IMAGE_BPP_GRAYSCALE: { + // Packaed alpha for SMUAD calls + const uint32_t va = (alpha << 16) + (256 - alpha); + + // Iterate the img area to be updated + for (int y = other_y_start; y < other_y_end; y++) { + // Pre-add x_off here to save adding it inside the central loop + uint8_t *img_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y_off + y) + x_off; + + const int other_y = fast_floorf(y * over_yscale); + void *other_row_ptr = imlib_compute_row_ptr(other, other_y); + + for (int x = other_x_start; x < other_x_end; x++) { + const int other_x = fast_floorf(x * over_xscale); + + if ((!mask) || image_get_mask_pixel(mask, other_x, other_y)) { + const uint8_t other_pixel = safe_map_pixel(IMAGE_BPP_GRAYSCALE, other_bpp, imlib_get_pixel_fast(other_bpp, other_row_ptr, other_x)); + + uint8_t result_pixel; + if (alpha==256) { + result_pixel = other_pixel; + } + else { + const uint8_t img_pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(img_row_ptr, x); + + const uint32_t vgs = (other_pixel << 16) + img_pixel; + result_pixel = __SMUAD(va, vgs)>>8; + } + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(img_row_ptr, x, result_pixel); + } + } + } + break; + } + case IMAGE_BPP_RGB565: { + alpha >>= 3; + uint32_t alpha_complement = 32 - alpha; + // Iterate the img area to be updated + for (int y = other_y_start; y < other_y_end; y++) { + // Pre-add x_off here to save adding it inside the central loop + uint16_t *img_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y_off + y) + x_off; + + const int other_y = fast_floorf(y * over_yscale); + void *other_row_ptr = imlib_compute_row_ptr(other, other_y); + + for (int x = other_x_start; x < other_x_end; x++) { + const int other_x = fast_floorf(x * over_xscale); + + if ((!mask) || image_get_mask_pixel(mask, other_x, other_y)) { + uint32_t other_pixel = imlib_get_pixel_fast(other_bpp, other_row_ptr, other_x); + other_pixel = color_palette + ? color_palette[other_pixel] + : safe_map_pixel(IMAGE_BPP_RGB565, other_bpp, other_pixel); + + uint32_t result_pixel; + if (alpha==32) { //256 + result_pixel = other_pixel; + } + else { + uint32_t img_pixel = IMAGE_GET_RGB565_PIXEL_FAST(img_row_ptr, x); + // 0000000000000000-gggbbbbbrrrrrggg + + // ** Extract green component of pixel to high word ** + img_pixel = img_pixel | (img_pixel << 16); + // gggbbbbbrrrrrggg-gggbbbbbrrrrrggg + // Rotate to fix endianness + img_pixel = __ROR(img_pixel, 8); + // rrrrrggggggbbbbb-rrrrrggggggbbbbb + // Clear 5 bits per component a multiply + img_pixel &= 0x7E0F81F; + // 00000gggggg00000-rrrrr000000bbbbb + + // ** Extract green component of pixel to high word ** + other_pixel = other_pixel | (other_pixel << 16); + // gggbbbbbrrrrrggg-gggbbbbbrrrrrggg + // Rotate to fix endianness + other_pixel = __ROR(other_pixel, 8); + // rrrrrggggggbbbbb-rrrrrggggggbbbbb + // Clear 5 bits per component for the multiply + other_pixel &= 0x7E0F81F; + // 00000gggggg00000-rrrrr000000bbbbb + + // Combine foreground and background with alpha applied + result_pixel = (img_pixel * alpha_complement) + (other_pixel * alpha); + // GGGGGG.....RRRRR-.....0BBBBB..... (.'s are remainders) + + // Round component values + result_pixel >>= 5; + // 00000GGGGGG.....-RRRRR.....0BBBBB + result_pixel &= 0x7E0F81F; + // 00000GGGGGG00000-RRRRR000000BBBBB + + // Merge green component back to low word + result_pixel = result_pixel | (result_pixel >> 16); + // 00000GGGGGG00000-RRRRRGGGGGGBBBBB + // Switch endianness + result_pixel = __REV16(result_pixel); + // GGG0000000000GGG-GGGGBBBBBRRRRRGG + // Don't bother rounding off high word it'll get removed on store + } + IMAGE_PUT_RGB565_PIXEL_FAST(img_row_ptr, x, result_pixel); + } + } + } + break; + } + default: { + break; } } } diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index 44e3fef9f..b80891c9a 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -1283,7 +1283,7 @@ void imlib_draw_circle(image_t *img, int cx, int cy, int r, int c, int thickness 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, float scale, int x_spacing, int y_spacing, bool mono_space, int char_rotation, bool char_hmirror, bool char_vflip, int string_rotation, bool string_hmirror, bool string_hflip); -void imlib_draw_image(image_t *img, image_t *other, int x_off, int y_off, float x_scale, float y_scale, float alpha, image_t *mask, const uint16_t *color_palette); +void imlib_draw_image(image_t *img, image_t *other, int x_off, int y_off, float x_scale, float y_scale, int alpha, image_t *mask, const uint16_t *color_palette); void imlib_flood_fill(image_t *img, int x, int y, float seed_threshold, float floating_threshold, int c, bool invert, bool clear_background, image_t *mask); diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index ef7242970..d6ba4b050 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -1924,13 +1924,11 @@ STATIC mp_obj_t py_image_draw_image(uint n_args, const mp_obj_t *args, mp_map_t float arg_x_scale = py_helper_keyword_float(n_args, args, offset + 0, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_scale), 1.0f); - PY_ASSERT_TRUE_MSG((0.0f <= arg_x_scale), "Error: 0.0 <= x_scale!"); float arg_y_scale = py_helper_keyword_float(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_scale), 1.0f); - PY_ASSERT_TRUE_MSG((0.0f <= arg_y_scale), "Error: 0.0 <= y_scale!"); - float arg_alpha = - py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_alpha), 256) / 256.0f; - PY_ASSERT_TRUE_MSG((0 <= arg_alpha) && (arg_alpha <= 1), "Error: 0 <= alpha <= 256!"); + int arg_alpha = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_alpha), 256); + PY_ASSERT_TRUE_MSG((0 <= arg_alpha) && (arg_alpha <= 256), "Error: 0 <= alpha <= 256!"); image_t *arg_msk = py_helper_keyword_to_image_mutable_mask(n_args, args, offset + 3, kw_args); int palette = py_helper_keyword_int(n_args, args, offset + 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color_palette), -1);