From a94b16a49d524881f31f75d5dbc748abbbfdefe1 Mon Sep 17 00:00:00 2001 From: Matt Dawson Date: Sun, 3 May 2020 21:24:51 -0700 Subject: [PATCH] draw_image_bilinear_interp_alpha_palletes_and_center_image --- .../03-Drawing/image_drawing_advanced.py | 18 +- src/omv/img/draw.c | 487 ++++++++++++++---- src/omv/img/imlib.h | 11 +- src/omv/py/py_helper.c | 6 + src/omv/py/py_helper.h | 4 +- src/omv/py/py_image.c | 71 ++- src/omv/py/qstrdefsomv.h | 4 + 7 files changed, 472 insertions(+), 129 deletions(-) diff --git a/scripts/examples/03-Drawing/image_drawing_advanced.py b/scripts/examples/03-Drawing/image_drawing_advanced.py index bb7815c79..f72f7f764 100644 --- a/scripts/examples/03-Drawing/image_drawing_advanced.py +++ b/scripts/examples/03-Drawing/image_drawing_advanced.py @@ -6,7 +6,7 @@ import sensor, image, time, pyb sensor.reset() sensor.set_pixformat(sensor.RGB565) # or GRAYSCALE... -sensor.set_framesize(sensor.QQVGA) # or QQVGA... +sensor.set_framesize(sensor.QVGA) # or QQVGA... sensor.skip_frames(time = 2000) clock = time.clock() @@ -22,16 +22,16 @@ CYCLE_MASK = True value_mixer = 0 # Location of small image -x=100 -y=50 +x = 100 +y = 50 # Bounce direction -xd=.1 -yd=.1 +xd = .1 +yd = .1 # Small image scaling rescale = 1.0 -rd=0.01 +rd = 0.01 max_rescale = 5 min_rescale = -max_rescale @@ -78,16 +78,14 @@ while(True): # 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) + img.draw_image(small_img, int(x), int(y), mask=small_img.to_bitmap(copy=True), x_scale=-rescale, y_scale=rescale, alpha=240, hint=image.INTERPOLATE_BILINEAR | image.IMAGE_CENTER) status += 'alpha:240 ' status += '+mask ' else: - img.draw_image(small_img, draw_x, draw_y, x_scale=-rescale, y_scale=rescale, alpha=128) + img.draw_image(small_img, int(x), int(y), x_scale=-rescale, y_scale=rescale, alpha=128, hint=image.INTERPOLATE_BILINEAR | image.IMAGE_CENTER) status += 'alpha:128 ' img.draw_string(8, 0, status, mono_space = False) diff --git a/src/omv/img/draw.c b/src/omv/img/draw.c index bbae94b36..d4f60d3ce 100644 --- a/src/omv/img/draw.c +++ b/src/omv/img/draw.c @@ -11,7 +11,7 @@ #include "font.h" #include "imlib.h" -void* imlib_compute_row_ptr(image_t *img, int y) { +void* imlib_compute_row_ptr(const image_t *img, int y) { switch(img->bpp) { case IMAGE_BPP_BINARY: { return IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); @@ -29,7 +29,7 @@ void* imlib_compute_row_ptr(image_t *img, int y) { } } -inline int imlib_get_pixel_fast(int img_bpp, void *row_ptr, int x) +inline int imlib_get_pixel_fast(int img_bpp, const void *row_ptr, int x) { switch(img_bpp) { case IMAGE_BPP_BINARY: { @@ -488,21 +488,215 @@ 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, int alpha, image_t *mask, const uint16_t *color_palette) +/** + * Blend two RGB888 format pixels using alpha. + * NOTE: + * Interpolating RGB is not a good way of blending colors as it can generate colors that aren't in the original image. + * It's better to blend by transforming to another color space then interpolate, but that may slow things down. + * We could implement a better blend at a later date using a hint like image.BLEND_USING_HSV. + * + * @param background_pixel Background pixel value in RGB888 + * @param foreground_pixel Foreground pixel value in RGB888 + * @param alpha Foreground alpha 0->128 + * @param alpha_complement 128 minues Foreground alpha + * @return Blended pixel in RGB888 format + */ +uint32_t draw_blendop_rgb888(uint32_t background_pixel, uint32_t foreground_pixel, uint32_t alpha, uint32_t alpha_complement) { - // 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); + // rrrrrrrrggggggggbbbbbbbb + uint32_t frb = foreground_pixel & 0xFF00FF; + // rrrrrrrr........bbbbbbbb + uint32_t fg = (foreground_pixel >> 8) & 255; + // ................gggggggg + uint32_t brb = background_pixel & 0xFF00FF; + // rrrrrrrr........bbbbbbbb + uint32_t bg = (background_pixel >> 8) & 255; + // ................gggggggg - const int img_bpp = img->bpp; - const int other_bpp = other->bpp; + uint32_t rb = (frb * alpha + brb * alpha_complement) >> 7; + uint32_t g = (fg * alpha + bg * alpha_complement) >> 7; + return (rb & 0xFF00FF) + (g << 8); +} + +/** + * Scale an RGB565 format pixel returning an RGB888 result. + * + * @param pixel RGB565 pixel to scale. + * @param scale Amount to scale 0->128 + * @return Scaled pixel as RGB888 + */ +uint32_t draw_scaleop_RGB565_to_RGB888(uint32_t pixel, uint32_t scale) +{ + uint32_t vr = COLOR_RGB565_TO_R8(pixel); + uint32_t vg = COLOR_RGB565_TO_G8(pixel); + uint32_t vb = COLOR_RGB565_TO_B8(pixel); + uint32_t r = (vr * scale) >> 7; + uint32_t g = (vg * scale) >> 7; + uint32_t b = (vb * scale) >> 7; + + return (r << 16) + (g << 8) + b; +} + +/** + * Convert a pixel to binary. + * Used by interpolation cache line methods to convert mask to bitmap. + * + * @param bpp Bits per pixel of pixel. + * @param pixel Pixel value. + * @return pixel in binary format. + */ +inline bool pixel_to_binary(int bpp, uint32_t pixel) { + switch (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 false; + } + } +} + +/** + * Used by INTERPOLATE_BILINEAR to generate a grayscale linear interpolated row. + * The drawing algorithm will later apply the vertical interpolation between two cached lines. + * + * @param cache_line Where to write the line + * @param alpha 0->128, alpha blending value for other image. + * @param other_row_ptr Other source image row pointer. + * @param other_bpp Other image bits per pixel. + * @param mask_row_ptr Mask image row pointer. + * @param mask_bpp Mask image bits per pixel. + * @param other_x_start Start x pixel location in source/mask image. + * @param other_x_end End x pixel (exclusive) location in source/mask image. + * @param over_x_scale Scale from other scale to image scale. + */ +static void int_generate_cache_line_grayscale(uint16_t *cache_line, int alpha, uint8_t *other_row_ptr, int other_bpp, void *mask_row_ptr, int mask_bpp, int other_x_start, int other_x_end, float over_xscale) +{ + // generate line + for (int i = 0, x = other_x_start; x < other_x_end; x++, i++) { + float other_x_float = x * over_xscale; + uint32_t other_x = fast_floorf(other_x_float); + uint32_t weight_x = fast_floorf((other_x_float - other_x) * alpha); + + bool mask1 = true, mask2 = true; + if (mask_row_ptr) { + mask1 = pixel_to_binary(mask_bpp, imlib_get_pixel_fast(mask_bpp, mask_row_ptr, other_x)); + mask2 = pixel_to_binary(mask_bpp, imlib_get_pixel_fast(mask_bpp, mask_row_ptr, other_x + 1)); + } + uint32_t alpha1 = mask1 ? (alpha - weight_x) : 0; + uint32_t alpha2 = mask2 ? weight_x : 0; + + uint32_t other_pixel1 = safe_map_pixel(IMAGE_BPP_GRAYSCALE, other_bpp, imlib_get_pixel_fast(other_bpp, other_row_ptr, other_x)) * alpha1; + uint32_t other_pixel2 = safe_map_pixel(IMAGE_BPP_GRAYSCALE, other_bpp, imlib_get_pixel_fast(other_bpp, other_row_ptr, other_x + 1)) * alpha2; + + uint32_t alpha_complement = 256 - (alpha1 + alpha2); + + cache_line[i] = ((other_pixel1 + other_pixel2) & 0xFF00) + (alpha_complement >> 1); + } +} + + +/** + * Used by INTERPOLATE_BILINEAR to generate a RGB888 linear interpolated row. + * The drawing algorithm will later apply the vertical interpolation between two cached lines. + * + * @param cache_line Where to write the line + * @param alpha 0->128, alpha blending value for other image. + * @param other_row_ptr Other source image row pointer. + * @param other_bpp Other image bits per pixel. + * @param mask_row_ptr Mask image row pointer. + * @param mask_bpp Mask image bits per pixel. + * @param other_x_start Start x pixel location in source/mask image. + * @param other_x_end End x pixel (exclusive) location in source/mask image. + * @param over_x_scale Scale from other scale to image scale. + */ +static void int_generate_cache_line_rgb565(uint32_t *cache_line, int alpha, const uint16_t *other_row_ptr, int other_bpp, const void *mask_row_ptr, int mask_bpp, int other_x_start, int other_x_end, float over_xscale, const uint16_t *color_palette, const uint8_t *alpha_palette) +{ + // generate line + for (int i = 0, x = other_x_start; x < other_x_end; x++, i++) { + float other_x_float = x * over_xscale; + uint32_t other_x = fast_floorf(other_x_float); + uint32_t weight_x = fast_floorf((other_x_float - other_x) * alpha); + bool mask1 = true, mask2 = true; + + if (mask_row_ptr) { + mask1 = pixel_to_binary(mask_bpp, imlib_get_pixel_fast(mask_bpp, mask_row_ptr, other_x)); + mask2 = pixel_to_binary(mask_bpp, imlib_get_pixel_fast(mask_bpp, mask_row_ptr, other_x + 1)); + } + + uint32_t alpha1 = mask1 ? (alpha - weight_x) : 0; + uint32_t alpha2 = mask2 ? weight_x : 0; + + uint32_t other_pixel1 = imlib_get_pixel_fast(other_bpp, other_row_ptr, other_x); + uint32_t other_pixel2 = imlib_get_pixel_fast(other_bpp, other_row_ptr, other_x + 1); + + if (alpha_palette) { + alpha1 = alpha1 * alpha_palette[other_pixel1]; + // Alpha needs to be from 0 -> 256, so add 1/256th of alpha here + alpha1 = (alpha1 + (alpha1 >> 8)) >> 8; + + alpha2 = alpha2 * alpha_palette[other_pixel2]; + // Alpha needs to be from 0 -> 256 + alpha2 = (alpha2 + (alpha2 >> 8)) >> 8; + } + + other_pixel1 = color_palette ? color_palette[other_pixel1] : safe_map_pixel(IMAGE_BPP_RGB565, other_bpp, other_pixel1); + other_pixel1 = draw_scaleop_RGB565_to_RGB888(other_pixel1, alpha1); + other_pixel2 = color_palette ? color_palette[other_pixel2] : safe_map_pixel(IMAGE_BPP_RGB565, other_bpp, other_pixel2); + other_pixel2 = draw_scaleop_RGB565_to_RGB888(other_pixel2, alpha2); + + uint32_t alpha_complement = 128 - (alpha1 + alpha2); + + cache_line[i] = ((other_pixel1 + other_pixel2) << 8) + alpha_complement; // RGBA + } +} + +/** + * Draw an image onto another image converting format if necessary. + * + * @param img The image to draw onto. + * @param other The image to draw. + * @param x_off X offset in destination. + * @param y_off Y offset in destination. + * @param x_scale X scale. + * @param y_scale Y scale. + * @param alpha Alpha, between 0 and 256 inclusive. + * @param mask Mask image, if interpolating must be the same size as the other image. + * @param color_palette Color palette for transforming grayscale images to RGB565. + * @param alpha_palette Alpha palette for masking grayscale images. + * @param hint Rendering hint. e.g. INTERPOLATE_BILINEAR, IMAGE_CENTER + */ +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, const uint8_t *alpha_palette, image_hint_type hint) +{ + // If alpha is 0 then nothing changes + if (alpha == 0) return; + + if (hint & INTERPOLATE_BILINEAR) { + // Cannot interpolate a 1x1 pixel. + if (other->w <= 1 || other->h <= 1) hint &= ~INTERPOLATE_BILINEAR; + } + + // Scaled other size + int other_width_scaled = fast_floorf(fast_fabsf(x_scale) * other->w); + int other_height_scaled = fast_floorf(fast_fabsf(y_scale) * other->h); + + if (hint & IMAGE_CENTER) { + x_off -= other_width_scaled >> 1; + y_off -= other_height_scaled >> 1; + } + + // Scaler to convert from img scale to other scale + float over_xscale = IM_DIV(1.0f, x_scale), over_yscale = IM_DIV(1.0f, y_scale); // 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)); // 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; @@ -527,6 +721,16 @@ void imlib_draw_image(image_t *img, image_t *other, int x_off, int y_off, float y_off += other_height_scaled; } + // If we're linear interpolating the last pixel will overflow if we land on it, we want to land just before it. + if (hint & INTERPOLATE_BILINEAR) { + over_xscale *= (float)(other->w - 1) / other->w; + over_yscale *= (float)(other->h - 1) / other->h; + } + + const int img_bpp = img->bpp; + const int other_bpp = other->bpp; + const int mask_bpp = mask ? mask->bpp : 0; + 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 @@ -534,15 +738,15 @@ void imlib_draw_image(image_t *img, image_t *other, int x_off, int y_off, float // 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)) { + 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); } } @@ -551,107 +755,196 @@ void imlib_draw_image(image_t *img, image_t *other, int x_off, int y_off, float break; } case IMAGE_BPP_GRAYSCALE: { - // Packaed alpha for SMUAD calls - const uint32_t va = (alpha << 16) + (256 - alpha); + if (hint & INTERPOLATE_BILINEAR) { + const int bytes_per_img_line = img->w * sizeof(uint8_t) * 2; // * 2 for mask byte + uint16_t *cache_line_1 = fb_alloc(bytes_per_img_line, FB_ALLOC_NO_HINT); + uint16_t *cache_line_2 = fb_alloc(bytes_per_img_line, FB_ALLOC_NO_HINT); + uint16_t *cache_line_top = cache_line_2; + uint16_t *cache_line_bottom = cache_line_1; - // 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; + // Pre-fill cache for first drawn line + int temp_other_y = fast_floorf(other_y_start * over_yscale); + uint8_t *other_row_ptr = imlib_compute_row_ptr(other, temp_other_y); + void *mask_row_ptr = mask ? imlib_compute_row_ptr(mask, temp_other_y) : NULL; - const int other_y = fast_floorf(y * over_yscale); - void *other_row_ptr = imlib_compute_row_ptr(other, other_y); + int_generate_cache_line_grayscale(cache_line_bottom, alpha, other_row_ptr, other_bpp, mask_row_ptr, mask_bpp, other_x_start, other_x_end, over_xscale); - for (int x = other_x_start; x < other_x_end; x++) { - const int other_x = fast_floorf(x * over_xscale); + int last_other_y = -1; - 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)); + // 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; + float other_y_float = y * over_yscale; + int other_y = fast_floorf(other_y_float); + int weight_y = fast_floorf((other_y_float - other_y) * 256); - uint8_t result_pixel; - if (alpha==256) { - result_pixel = other_pixel; + if (y_scale < 0) weight_y = 256 - weight_y; + uint32_t y_interpolate = (weight_y << 16) + (256 - weight_y); + + if (last_other_y != other_y) { + last_other_y = other_y; + + // Move to next line. Swap y+1 cache line to y + uint16_t *cache_line_temp = cache_line_top; + cache_line_top = cache_line_bottom; + cache_line_bottom = cache_line_temp; + + // And generate a new y+1 + other_row_ptr = imlib_compute_row_ptr(other, other_y + 1); + mask_row_ptr = mask ? imlib_compute_row_ptr(mask, other_y + 1) : NULL; + int_generate_cache_line_grayscale(cache_line_bottom, alpha, other_row_ptr, other_bpp, mask_row_ptr, mask_bpp, other_x_start, other_x_end, over_xscale); + } + + for (int i = 0, x = other_x_start; x < other_x_end; x++, i++) { + uint32_t pixel_data = (cache_line_bottom[i] << 16) | cache_line_top[i]; + uint32_t alpha_pixels = pixel_data & 0xFF00FF; + uint32_t alpha_complement_15bits = __SMUAD(y_interpolate, alpha_pixels); // 8 bits x 7 bits = 15 bits + uint32_t other_pixels = (pixel_data >> 8) & 0xFF00FF; + uint32_t pixel_16bits = __SMUAD(y_interpolate, other_pixels); + uint8_t img_pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(img_row_ptr, x); + uint32_t pixel = (((img_pixel * alpha_complement_15bits) >> 7) + pixel_16bits) >> 8; + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(img_row_ptr, x, pixel); + } + } + fb_free(cache_line_1); + fb_free(cache_line_2); + } + else { + uint32_t packed_alpha = (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; + int other_y = fast_floorf(y * over_yscale); + uint16_t *other_row_ptr = imlib_compute_row_ptr(other, other_y); + + for (int x = other_x_start; x < other_x_end; x++) { + int other_x = fast_floorf(x * over_xscale); + + if (!mask || image_get_mask_pixel(mask, other_x, other_y)) { + uint8_t result_pixel = safe_map_pixel(IMAGE_BPP_GRAYSCALE, other_bpp, imlib_get_pixel_fast(other_bpp, other_row_ptr, other_x)); + + if (alpha != 256) { + uint8_t img_pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(img_row_ptr, x); + uint32_t vgs = (result_pixel << 16) + img_pixel; + + result_pixel = __SMUAD(packed_alpha, vgs) >> 8; + } + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(img_row_ptr, x, result_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; + // Alpha is 0->128 + alpha >>= 1; + + if (hint & INTERPOLATE_BILINEAR) { + int bytes_per_img_line = img->w * sizeof(uint16_t) * 2; // * 2 for mask byte - const int other_y = fast_floorf(y * over_yscale); - void *other_row_ptr = imlib_compute_row_ptr(other, other_y); + uint32_t *cache_line_1 = fb_alloc(bytes_per_img_line, FB_ALLOC_NO_HINT); + uint32_t *cache_line_2 = fb_alloc(bytes_per_img_line, FB_ALLOC_NO_HINT); + uint32_t *cache_line_top = cache_line_2; + uint32_t *cache_line_bottom = cache_line_1; - for (int x = other_x_start; x < other_x_end; x++) { - const int other_x = fast_floorf(x * over_xscale); + int temp_other_y = fast_floorf(other_y_start * over_yscale); + uint16_t *other_row_ptr = imlib_compute_row_ptr(other, temp_other_y); + void *mask_row_ptr = mask ? imlib_compute_row_ptr(mask, temp_other_y) : NULL; + int_generate_cache_line_rgb565(cache_line_bottom, alpha, other_row_ptr, other_bpp, mask_row_ptr, mask_bpp, other_x_start, other_x_end, over_xscale, color_palette, alpha_palette); - 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); + int last_other_y = -1; + // 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; + float other_y_float = y * over_yscale; + int other_y = fast_floorf(other_y_float); + int weight_y = fast_floorf((other_y_float - other_y) * 256); + + if (y_scale < 0) weight_y = 256 - weight_y; + uint32_t y_interpolate = ((256 - weight_y) << 16) + weight_y; + weight_y >>= 1; + + // If the scale is negative the pixels come in reverse order, so reverse the remainder weighting + int weight_y_complement = 128 - weight_y; + + if (last_other_y != other_y) { + uint32_t *cache_line_temp = cache_line_top; - uint32_t result_pixel; - if (alpha==32) { //256 - result_pixel = other_pixel; + cache_line_top = cache_line_bottom; + cache_line_bottom = cache_line_temp; + + other_row_ptr = imlib_compute_row_ptr(other, other_y + 1); + mask_row_ptr = mask ? imlib_compute_row_ptr(mask, other_y + 1) : NULL; + int_generate_cache_line_rgb565(cache_line_bottom, alpha, other_row_ptr, other_bpp, mask_row_ptr, mask_bpp, other_x_start, other_x_end, over_xscale, color_palette, alpha_palette); + + last_other_y = other_y; + } + + for (int i = 0, x = other_x_start; x < other_x_end; x++, i++) { + uint32_t top = cache_line_top[i]; + uint32_t bottom = cache_line_bottom[i]; + uint32_t result_pixel = draw_blendop_rgb888(top >> 8, bottom >> 8, weight_y, weight_y_complement); + + uint32_t alpha_pixels = ((top & 255) << 16) | (bottom & 255); + uint32_t alpha_alpha = __SMUAD(y_interpolate, alpha_pixels) >> 8; + + //if (alpha_alpha != 0) { + uint32_t img_pixel = IMAGE_GET_RGB565_PIXEL_FAST(img_row_ptr, x); + + // Apply alpha + img_pixel = draw_scaleop_RGB565_to_RGB888(img_pixel, alpha_alpha); + // Add to other pixel (which already had alpha applied in generate_line_cache) + result_pixel = img_pixel + result_pixel; + result_pixel = COLOR_R5_G6_B5_TO_RGB565(result_pixel >> 19, (result_pixel >> 10) & 63, (result_pixel >> 3) & 63); + + IMAGE_PUT_RGB565_PIXEL_FAST(img_row_ptr, x, result_pixel); // Convert RGB + } + } + + fb_free(cache_line_1); + fb_free(cache_line_2); + } + else { + uint32_t va = __PKHBT((128-alpha), alpha, 16); + + // 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; + float other_y_float = y * over_yscale; + int other_y = fast_floorf(other_y_float); + uint16_t *other_row_ptr = imlib_compute_row_ptr(other, other_y); + + for (int x = other_x_start; x < other_x_end; x++) { + int other_x = fast_floorf(x * over_xscale); + + if (!mask || image_get_mask_pixel(mask, other_x, other_y)) { + uint32_t result_pixel = imlib_get_pixel_fast(other_bpp, other_row_ptr, other_x); + + result_pixel = color_palette ? color_palette[result_pixel] : safe_map_pixel(IMAGE_BPP_RGB565, other_bpp, result_pixel); + + if (alpha != 128) { + uint16_t img_pixel = IMAGE_GET_RGB565_PIXEL_FAST(img_row_ptr, x); + uint32_t r_ta = COLOR_RGB565_TO_R5(result_pixel); + uint32_t g_ta = COLOR_RGB565_TO_G6(result_pixel); + uint32_t b_ta = COLOR_RGB565_TO_B5(result_pixel); + uint32_t vr = __PKHBT(COLOR_RGB565_TO_R5(img_pixel), r_ta, 16); + uint32_t vg = __PKHBT(COLOR_RGB565_TO_G6(img_pixel), g_ta, 16); + uint32_t vb = __PKHBT(COLOR_RGB565_TO_B5(img_pixel), b_ta, 16); + uint32_t r = __SMUAD(va, vr) >> 7; + uint32_t g = __SMUAD(va, vg) >> 7; + uint32_t b = __SMUAD(va, vb) >> 7; + + result_pixel = COLOR_R5_G6_B5_TO_RGB565(r, g, b); + } + IMAGE_PUT_RGB565_PIXEL_FAST(img_row_ptr, x, result_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); } } } diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index b80891c9a..3619711c8 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -259,7 +259,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 & 0x7) << 13) | (_b5 << 8); \ + __REV16((_r5 << 11) | (_g6 << 5) | _b5); \ }) #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)) @@ -1141,6 +1141,12 @@ typedef struct find_barcodes_list_lnk_data { int quality; } find_barcodes_list_lnk_data_t; +typedef enum image_hint { + INTERPOLATE_BILINEAR = 1, + IMAGE_CENTER = 128 +} image_hint_type; + + /* Color space functions */ int8_t imlib_rgb565_to_l(uint16_t pixel); int8_t imlib_rgb565_to_a(uint16_t pixel); @@ -1283,7 +1289,8 @@ 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, int 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, const uint8_t *alpha_palette, image_hint_type hint); 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_helper.c b/src/omv/py/py_helper.c index f815ebeef..f71982269 100644 --- a/src/omv/py/py_helper.c +++ b/src/omv/py/py_helper.c @@ -73,6 +73,12 @@ image_t *py_helper_keyword_to_image_mutable_color_palette(uint n_args, const mp_ return py_helper_keyword_to_image_mutable(n_args, args, arg_index, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color_palette), NULL); } +image_t *py_helper_keyword_to_image_mutable_alpha_palette(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args) +{ + return py_helper_keyword_to_image_mutable(n_args, args, arg_index, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_alpha_palette), NULL); +} + void py_helper_keyword_rectangle(image_t *img, uint n_args, const mp_obj_t *args, uint arg_index, mp_map_t *kw_args, mp_obj_t kw, rectangle_t *r) { diff --git a/src/omv/py/py_helper.h b/src/omv/py/py_helper.h index 5eec5ce72..45fafc231 100644 --- a/src/omv/py/py_helper.h +++ b/src/omv/py/py_helper.h @@ -22,7 +22,9 @@ image_t *py_helper_keyword_to_image_mutable(uint n_args, const mp_obj_t *args, u image_t *py_helper_keyword_to_image_mutable_mask(uint n_args, const mp_obj_t *args, uint arg_index, mp_map_t *kw_args); image_t *py_helper_keyword_to_image_mutable_color_palette(uint n_args, const mp_obj_t *args, uint arg_index, - mp_map_t *kw_args); + mp_map_t *kw_args); +image_t *py_helper_keyword_to_image_mutable_alpha_palette(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args); void py_helper_keyword_rectangle(image_t *img, uint n_args, const mp_obj_t *args, uint arg_index, mp_map_t *kw_args, mp_obj_t kw, rectangle_t *r); void py_helper_keyword_rectangle_roi(image_t *img, uint n_args, const mp_obj_t *args, uint arg_index, diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index 6d24e0ff3..3d8393348 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -1933,31 +1933,62 @@ STATIC mp_obj_t py_image_draw_image(uint n_args, const mp_obj_t *args, mp_map_t py_helper_keyword_to_image_mutable_mask(n_args, args, offset + 3, kw_args); const uint16_t *color_palette = NULL; - int palette; - - if (py_helper_keyword_int_maybe(n_args, args, offset + 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color_palette), &palette)) { - if (palette == COLOR_PALETTE_RAINBOW) { - color_palette = rainbow_table; - } else if (palette == COLOR_PALETTE_IRONBOW) { - color_palette = ironbow_table; - } else { - nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Invalid color palette!")); - } - } else { - image_t *arg_palette = py_helper_keyword_to_image_mutable_color_palette(n_args, args, offset + 4, kw_args); + { + int palette; - if (arg_palette) { - if (arg_palette->bpp != IMAGE_BPP_RGB565) nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Palette must be an RGB565 format image!")); - if ((arg_palette->w * arg_palette->h) != 256) nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Palette image must have 256 pixels!")); - color_palette = (uint16_t*)arg_palette->data; + if (py_helper_keyword_int_maybe(n_args, args, offset + 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color_palette), &palette)) { + if (palette == COLOR_PALETTE_RAINBOW) { + color_palette = rainbow_table; + } else if (palette == COLOR_PALETTE_IRONBOW) { + color_palette = ironbow_table; + } else { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Invalid pre-defined color palette!")); + } + } else { + image_t *arg_color_palette = py_helper_keyword_to_image_mutable_color_palette(n_args, args, offset + 4, kw_args); + + if (arg_color_palette) { + if (arg_color_palette->bpp != IMAGE_BPP_RGB565) nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Color palette must be an RGB565 format image!")); + if ((arg_color_palette->w * arg_color_palette->h) != 256) nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Color palette image must have 256 pixels!")); + + color_palette = (uint16_t*)arg_color_palette->data; + } + } + + if (color_palette) { + if (arg_other->bpp != IMAGE_BPP_GRAYSCALE) nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Can only specify a color palette when passing a grayscale image!")); } } - if (color_palette) { - if (arg_other->bpp != IMAGE_BPP_GRAYSCALE) nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Can only specify color palette when passing a grayscale image!")); + const uint8_t *alpha_palette = NULL; + { + image_t *arg_alpha_palette = py_helper_keyword_to_image_mutable_alpha_palette(n_args, args, offset + 5, kw_args); + + if (arg_alpha_palette) { + if (arg_other->bpp != IMAGE_BPP_GRAYSCALE) nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Can only specify an alpha palette when passing a grayscale image!")); + if (arg_alpha_palette->bpp != IMAGE_BPP_GRAYSCALE) nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Alpha palette must be an grayscale format image!")); + if ((arg_alpha_palette->w * arg_alpha_palette->h) != 256) nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Alpha palette image must have 256 pixels!")); + + alpha_palette = (uint8_t*)arg_alpha_palette->data; + } } - imlib_draw_image(arg_img, arg_other, arg_cx, arg_cy, arg_x_scale, arg_y_scale, arg_alpha, arg_msk, color_palette); + if ((color_palette || alpha_palette) && arg_img->bpp != IMAGE_BPP_RGB565) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Palettes must be used with color images!")); + } + + image_hint_type hint = + py_helper_keyword_int(n_args, args, offset + 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_hint), 0); + + if (hint && arg_msk) { + // This check is only performed if there is a hint for backwards compatiblity with old draw image where dimesions were not enforced. + if (arg_msk->w != arg_other->w || arg_msk->h != arg_other->h) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Mask must have same dimensions as image")); + } + } + + imlib_draw_image(arg_img, arg_other, arg_cx, arg_cy, arg_x_scale, arg_y_scale, arg_alpha, arg_msk, color_palette, alpha_palette, hint); + return args[0]; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_image_obj, 3, py_image_draw_image); @@ -7594,6 +7625,8 @@ static const mp_rom_map_elem_t globals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR_CODE93), MP_ROM_INT(BARCODE_CODE93)}, {MP_ROM_QSTR(MP_QSTR_CODE128), MP_ROM_INT(BARCODE_CODE128)}, #endif + {MP_ROM_QSTR(MP_QSTR_INTERPOLATE_BILINEAR),MP_ROM_INT(INTERPOLATE_BILINEAR)}, + {MP_ROM_QSTR(MP_QSTR_IMAGE_CENTER), MP_ROM_INT(IMAGE_CENTER)}, {MP_ROM_QSTR(MP_QSTR_ImageWriter), MP_ROM_PTR(&py_image_imagewriter_obj)}, {MP_ROM_QSTR(MP_QSTR_ImageReader), MP_ROM_PTR(&py_image_imagereader_obj)}, {MP_ROM_QSTR(MP_QSTR_binary_to_grayscale), MP_ROM_PTR(&py_image_binary_to_grayscale_obj)}, diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index f60d07728..a02cc845f 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -450,6 +450,7 @@ Q(to_rainbow) // duplicate Q(copy) // duplicate Q(rgb_channel) Q(color_palette) +Q(alpha_palette) // Compress (in place) Q(compress) @@ -551,6 +552,9 @@ Q(draw_image) // duplicate Q(y_scale) Q(alpha) // duplicate Q(mask) +Q(hint) +Q(INTERPOLATE_BILINEAR) +Q(IMAGE_CENTER) // Draw Keypoints Q(draw_keypoints)