draw_image performance and negative scaling. (#758)

Improve draw_image performance and negative scaling.
This commit is contained in:
Matt Dawson 2020-04-23 03:43:14 +12:00 committed by GitHub
parent c6f727e4fa
commit 414c6e1a51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 292 additions and 63 deletions

View File

@ -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 (x<xmin or x>xmax):
xd = -xd
y = y + yd
if (y<ymin or y>ymax):
yd = -yd
# Update small image scale
if RESCALE:
rescale = rescale + rd
if (rescale<min_rescale or rescale>max_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())

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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);