Merge pull request #783 from matsondawson/draw_image_bilinear_interp_and_alpha_palletes

draw_image bi-linear interpolation, alpha palettes, and center_image
This commit is contained in:
Ibrahim Abd Elkader 2020-05-12 19:56:36 +02:00 committed by GitHub
commit 1c02cfed33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 533 additions and 139 deletions

View File

@ -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,18 +22,18 @@ 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.1
max_rescale = 5
min_rescale = -max_rescale
min_rescale = rd * 2
# Boundary to bounce within
xmin = -sensor.width() / SMALL_IMAGE_SCALE - 8
@ -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.IMAGE_HINT_BILINEAR | image.IMAGE_HINT_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.IMAGE_HINT_BILINEAR | image.IMAGE_HINT_CENTER)
status += 'alpha:128 '
img.draw_string(8, 0, status, mono_space = False)

View File

@ -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,22 +488,224 @@ 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);
// Scale is 0->128 so we shift right 7
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 IMAGE_HINT_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, const uint8_t *alpha_palette)
{
for (int i = 0, x = other_x_start; x < other_x_end; x++, i++) {
float other_x_float = (x + 0.5) * 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));
uint32_t other_pixel2 = safe_map_pixel(IMAGE_BPP_GRAYSCALE, other_bpp, imlib_get_pixel_fast(other_bpp, other_row_ptr, other_x + 1));
if (alpha_palette) {
alpha1 = alpha1 * alpha_palette[other_pixel1] >> 8;
alpha2 = alpha2 * alpha_palette[other_pixel2] >> 8;
}
other_pixel1 *= alpha1;
other_pixel2 *= alpha2;
// Image alpha is the remaining alpha after applying other alpha
uint32_t img_alpha = 256 - (alpha1 + alpha2);
// Note img_alpha is now 0->128 to fit into a byte
cache_line[i] = ((other_pixel1 + other_pixel2) & 0xFF00) + (img_alpha >> 1);
}
}
/**
* Used by IMAGE_HINT_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 + 0.5) * 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] >> 8;
alpha2 = alpha2 * alpha_palette[other_pixel2] >> 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);
// Image alpha is the remaining alpha after applying other alpha
uint32_t img_alpha = 128 - (alpha1 + alpha2);
cache_line[i] = ((other_pixel1 + other_pixel2) << 8) + img_alpha; // RGBA8888
}
}
/**
* 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. IMAGE_HINT_BILINEAR, IMAGE_HINT_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_t hint)
{
// If alpha is 0 then nothing changes
if (alpha == 0) return;
if (hint & IMAGE_HINT_BILINEAR) {
// Cannot interpolate a 1x1 pixel.
if (other->w <= 1 || other->h <= 1) hint &= ~IMAGE_HINT_BILINEAR;
}
// Scaled other size
int other_width_scaled = fast_floorf(x_scale * other->w);
int other_height_scaled = fast_floorf(y_scale * other->h);
// Center other if hint is set
if (hint & IMAGE_HINT_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;
int other_y_end = (y_off + other_height_scaled >= img->h) ? img->h - y_off : other_height_scaled;
@ -512,20 +714,15 @@ void imlib_draw_image(image_t *img, image_t *other, int x_off, int y_off, float
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;
// 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 we're linear interpolating the last pixel will overflow if we land on it, we want to land just before it.
if (hint & IMAGE_HINT_BILINEAR) {
over_xscale *= (float)(other->w - 1) / other->w;
over_yscale *= (float)(other->h - 1) / other->h;
}
if (over_yscale < 0) {
other_height_scaled--;
other_y_start -= other_height_scaled;
other_y_end -= other_height_scaled;
y_off += other_height_scaled;
}
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: {
@ -534,15 +731,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,110 +748,252 @@ 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 & IMAGE_HINT_BILINEAR) {
fb_alloc_mark();
// Allocate cache lines
int bytes_per_img_line = img->w * sizeof(uint8_t) * 2; // (1 byte graysclae + 1 byte alpha) = * 2
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;
// 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;
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, alpha_palette);
// Used to detect when other starts rendering from the next line
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
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);
// calculate y offset in other
float other_y_float = (y + 0.5) * over_yscale;
// Calculate weighting between top and bottom pixel
int other_y = fast_floorf(other_y_float);
int weight_y = fast_floorf((other_y_float - other_y) * 256);
uint32_t y_interpolate = (weight_y << 16) + (256 - weight_y);
// If we've moved to the next line in the other image then generate the new cache line
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, alpha_palette);
}
// Draw the line to img
for (int i = 0, x = other_x_start; x < other_x_end; x++, i++) {
// Pack pixel data for SMUADS
uint32_t pixel_data = (cache_line_bottom[i] << 16) | cache_line_top[i];
// Extract alpha for top + bottom pixel for SMUAD
uint32_t img_alpha_pixels = pixel_data & 0xFF00FF;
// Calculate the alpha weighting for the img pixel, don't unshift so we have more accuracy when combining
uint32_t img_alpha_15bits = __SMUAD(y_interpolate, img_alpha_pixels); // 8 bits x 7 bits = 15 bits
// Extract other pixels for SMUAD
uint32_t other_pixels = (pixel_data >> 8) & 0xFF00FF;
// Apply alpha, but don't unshift for more accuracy in combining
uint32_t pixel_16bits = __SMUAD(y_interpolate, other_pixels);
// Get img pixel.
uint8_t img_pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(img_row_ptr, x);
// Combine img_pixel with other pixel, and shift fixed component
uint32_t pixel = (((img_pixel * img_alpha_15bits) >> 7) + pixel_16bits) >> 8;
// Store pixel
IMAGE_PUT_GRAYSCALE_PIXEL_FAST(img_row_ptr, x, pixel);
}
}
// De-allocate cache lines
fb_alloc_free_till_mark();
} else {
// 00000000otheralph00000000imgalpha
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++) {
const int other_x = fast_floorf(x * over_xscale);
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));
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));
uint8_t result_pixel;
if (alpha==256) {
result_pixel = other_pixel;
if (alpha_palette) {
uint32_t temp_alpha = (alpha * alpha_palette[result_pixel]) >> 8;
packed_alpha = (temp_alpha << 16) + (256 - temp_alpha);
}
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;
if (packed_alpha & 0x1ff) {
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);
}
}
}
}
break;
}
case IMAGE_BPP_RGB565: {
alpha >>= 3;
uint32_t alpha_complement = 32 - alpha;
// Alpha is 0->128
alpha >>= 1;
if (hint & IMAGE_HINT_BILINEAR) {
fb_alloc_mark();
// Allocate cache lines
int bytes_per_img_line = img->w * 4; // (3 bytes RGB888 + 1 byte alpha) = * 4
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;
// Pre-fill cache for first drawn line
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);
// Used to detect when other starts rendering from the next line
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;
const int other_y = fast_floorf(y * over_yscale);
void *other_row_ptr = imlib_compute_row_ptr(other, other_y);
// calculate y offset in other
float other_y_float = (y + 0.5) * over_yscale;
// Calculate weighting between top and bottom pixel
int other_y = fast_floorf(other_y_float);
int weight_y = fast_floorf((other_y_float - other_y) * 256);
uint32_t y_interpolate = ((256 - weight_y) << 16) + weight_y;
// Weighting is 0->128 for blendops to prevent overflow
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;
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);
// Pack top and bottom img alpha for SMUAD
uint32_t img_alpha_top_bottom = ((top & 255) << 16) | (bottom & 255);
// Blend img alphas
uint32_t img_alpha = __SMUAD(y_interpolate, img_alpha_top_bottom) >> 8;
// if img alpha will have an effect
if (img_alpha) {
// Get img pixel
uint32_t img_pixel = IMAGE_GET_RGB565_PIXEL_FAST(img_row_ptr, x);
// Apply alpha to img pixel
img_pixel = draw_scaleop_RGB565_to_RGB888(img_pixel, img_alpha);
// Add to other pixel (which already had alpha applied in generate_line_cache)
result_pixel = img_pixel + result_pixel;
}
// Convert back to RGB565
result_pixel = COLOR_R5_G6_B5_TO_RGB565(result_pixel >> 19, (result_pixel >> 10) & 63, (result_pixel >> 3) & 63);
// Store pixel
IMAGE_PUT_RGB565_PIXEL_FAST(img_row_ptr, x, result_pixel);
}
}
// De-allocate cache lines
fb_alloc_free_till_mark();
} 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++) {
const int other_x = fast_floorf(x * over_xscale);
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);
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);
uint32_t result_pixel;
if (alpha==32) { //256
result_pixel = other_pixel;
if (alpha_palette) {
uint32_t temp_alpha = (alpha * alpha_palette[result_pixel]) >> 8;
va = __PKHBT((128 - temp_alpha), temp_alpha, 16);
}
else {
uint32_t img_pixel = IMAGE_GET_RGB565_PIXEL_FAST(img_row_ptr, x);
// 0000000000000000-gggbbbbbrrrrrggg
result_pixel = color_palette ? color_palette[result_pixel] : safe_map_pixel(IMAGE_BPP_RGB565, other_bpp, result_pixel);
// ** 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
if (va & 0x1ff) {
// Blend img to other pixel
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;
// ** 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
result_pixel = COLOR_R5_G6_B5_TO_RGB565(r, g, b);
}
IMAGE_PUT_RGB565_PIXEL_FAST(img_row_ptr, x, result_pixel);
}
}
}
}
break;
}
default: {

View File

@ -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 {
IMAGE_HINT_BILINEAR = 1,
IMAGE_HINT_CENTER = 128
} image_hint_t;
/* 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_t 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);

View File

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

View File

@ -23,6 +23,8 @@ image_t *py_helper_keyword_to_image_mutable_mask(uint n_args, const mp_obj_t *ar
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);
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,

View File

@ -1938,8 +1938,12 @@ 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!");
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!");
@ -1947,6 +1951,7 @@ 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)) {
@ -1955,23 +1960,54 @@ STATIC mp_obj_t py_image_draw_image(uint n_args, const mp_obj_t *args, mp_map_t
} else if (palette == COLOR_PALETTE_IRONBOW) {
color_palette = ironbow_table;
} else {
nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Invalid color palette!"));
nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Invalid pre-defined color palette!"));
}
} else {
image_t *arg_palette = py_helper_keyword_to_image_mutable_color_palette(n_args, args, offset + 4, kw_args);
image_t *arg_color_palette = py_helper_keyword_to_image_mutable_color_palette(n_args, args, offset + 4, kw_args);
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 (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 color palette when passing a grayscale image!"));
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!"));
}
}
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 && arg_img->bpp != IMAGE_BPP_RGB565) {
nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Color palettes must be used with color images!"));
}
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!"));
if (arg_img->bpp != IMAGE_BPP_GRAYSCALE && arg_img->bpp != IMAGE_BPP_RGB565) nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Alpha palettes must be used with color images!"));
alpha_palette = (uint8_t*)arg_alpha_palette->data;
}
}
image_hint_t 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);
@ -7608,6 +7644,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_IMAGE_HINT_BILINEAR),MP_ROM_INT(IMAGE_HINT_BILINEAR)},
{MP_ROM_QSTR(MP_QSTR_IMAGE_HINT_CENTER), MP_ROM_INT(IMAGE_HINT_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)},

View File

@ -452,6 +452,7 @@ Q(to_rainbow)
// duplicate Q(copy)
// duplicate Q(rgb_channel)
Q(color_palette)
Q(alpha_palette)
// Compress (in place)
Q(compress)
@ -553,6 +554,9 @@ Q(draw_image)
// duplicate Q(y_scale)
Q(alpha)
// duplicate Q(mask)
Q(hint)
Q(IMAGE_HINT_BILINEAR)
Q(IMAGE_HINT_CENTER)
// Draw Keypoints
Q(draw_keypoints)