mirror of
https://github.com/openmv/openmv.git
synced 2025-11-04 14:49:50 +08:00
1062 lines
44 KiB
C
1062 lines
44 KiB
C
/*
|
|
* This file is part of the OpenMV project.
|
|
*
|
|
* Copyright (c) 2013-2019 Ibrahim Abdelkader <iabdalkader@openmv.io>
|
|
* Copyright (c) 2013-2019 Kwabena W. Agyeman <kwagyeman@openmv.io>
|
|
*
|
|
* This work is licensed under the MIT license, see the file LICENSE for details.
|
|
*
|
|
* Basic drawing functions.
|
|
*/
|
|
#include "font.h"
|
|
#include "imlib.h"
|
|
|
|
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);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline int imlib_get_pixel_fast(int img_bpp, const 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)
|
|
{
|
|
if ((0 <= x) && (x < img->w) && (0 <= y) && (y < img->h)) {
|
|
switch(img->bpp) {
|
|
case IMAGE_BPP_BINARY: {
|
|
IMAGE_PUT_BINARY_PIXEL(img, x, y, p);
|
|
break;
|
|
}
|
|
case IMAGE_BPP_GRAYSCALE: {
|
|
IMAGE_PUT_GRAYSCALE_PIXEL(img, x, y, p);
|
|
break;
|
|
}
|
|
case IMAGE_BPP_RGB565: {
|
|
IMAGE_PUT_RGB565_PIXEL(img, x, y, p);
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://stackoverflow.com/questions/1201200/fast-algorithm-for-drawing-filled-circles
|
|
static void point_fill(image_t *img, int cx, int cy, int r0, int r1, int c)
|
|
{
|
|
for (int y = r0; y <= r1; y++) {
|
|
for (int x = r0; x <= r1; x++) {
|
|
if (((x * x) + (y * y)) <= (r0 * r0)) {
|
|
imlib_set_pixel(img, cx + x, cy + y, c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C
|
|
void imlib_draw_line(image_t *img, int x0, int y0, int x1, int y1, int c, int thickness)
|
|
{
|
|
if (thickness > 0) {
|
|
int thickness0 = (thickness - 0) / 2;
|
|
int thickness1 = (thickness - 1) / 2;
|
|
int dx = abs(x1 - x0), sx = (x0 < x1) ? 1 : -1;
|
|
int dy = abs(y1 - y0), sy = (y0 < y1) ? 1 : -1;
|
|
int err = ((dx > dy) ? dx : -dy) / 2;
|
|
|
|
for (;;) {
|
|
point_fill(img, x0, y0, -thickness0, thickness1, c);
|
|
if ((x0 == x1) && (y0 == y1)) break;
|
|
int e2 = err;
|
|
if (e2 > -dx) { err -= dy; x0 += sx; }
|
|
if (e2 < dy) { err += dx; y0 += sy; }
|
|
}
|
|
}
|
|
}
|
|
|
|
static void xLine(image_t *img, int x1, int x2, int y, int c)
|
|
{
|
|
while (x1 <= x2) imlib_set_pixel(img, x1++, y, c);
|
|
}
|
|
|
|
static void yLine(image_t *img, int x, int y1, int y2, int c)
|
|
{
|
|
while (y1 <= y2) imlib_set_pixel(img, x, y1++, c);
|
|
}
|
|
|
|
void imlib_draw_rectangle(image_t *img, int rx, int ry, int rw, int rh, int c, int thickness, bool fill)
|
|
{
|
|
if (fill) {
|
|
|
|
for (int y = ry, yy = ry + rh; y < yy; y++) {
|
|
for (int x = rx, xx = rx + rw; x < xx; x++) {
|
|
imlib_set_pixel(img, x, y, c);
|
|
}
|
|
}
|
|
|
|
} else if (thickness > 0) {
|
|
int thickness0 = (thickness - 0) / 2;
|
|
int thickness1 = (thickness - 1) / 2;
|
|
|
|
for (int i = rx - thickness0, j = rx + rw + thickness1, k = ry + rh - 1; i < j; i++) {
|
|
yLine(img, i, ry - thickness0, ry + thickness1, c);
|
|
yLine(img, i, k - thickness0, k + thickness1, c);
|
|
}
|
|
|
|
for (int i = ry - thickness0, j = ry + rh + thickness1, k = rx + rw - 1; i < j; i++) {
|
|
xLine(img, rx - thickness0, rx + thickness1, i, c);
|
|
xLine(img, k - thickness0, k + thickness1, i, c);
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://stackoverflow.com/questions/27755514/circle-with-thickness-drawing-algorithm
|
|
void imlib_draw_circle(image_t *img, int cx, int cy, int r, int c, int thickness, bool fill)
|
|
{
|
|
if (fill) {
|
|
point_fill(img, cx, cy, -r, r, c);
|
|
} else if (thickness > 0) {
|
|
int thickness0 = (thickness - 0) / 2;
|
|
int thickness1 = (thickness - 1) / 2;
|
|
|
|
int xo = r + thickness0;
|
|
int xi = IM_MAX(r - thickness1, 0);
|
|
int xi_tmp = xi;
|
|
int y = 0;
|
|
int erro = 1 - xo;
|
|
int erri = 1 - xi;
|
|
|
|
while(xo >= y) {
|
|
xLine(img, cx + xi, cx + xo, cy + y, c);
|
|
yLine(img, cx + y, cy + xi, cy + xo, c);
|
|
xLine(img, cx - xo, cx - xi, cy + y, c);
|
|
yLine(img, cx - y, cy + xi, cy + xo, c);
|
|
xLine(img, cx - xo, cx - xi, cy - y, c);
|
|
yLine(img, cx - y, cy - xo, cy - xi, c);
|
|
xLine(img, cx + xi, cx + xo, cy - y, c);
|
|
yLine(img, cx + y, cy - xo, cy - xi, c);
|
|
|
|
y++;
|
|
|
|
if (erro < 0) {
|
|
erro += 2 * y + 1;
|
|
} else {
|
|
xo--;
|
|
erro += 2 * (y - xo + 1);
|
|
}
|
|
|
|
if (y > xi_tmp) {
|
|
xi = y;
|
|
} else {
|
|
if (erri < 0) {
|
|
erri += 2 * y + 1;
|
|
} else {
|
|
xi--;
|
|
erri += 2 * (y - xi + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://scratch.mit.edu/projects/50039326/
|
|
static void scratch_draw_pixel(image_t *img, int x0, int y0, int dx, int dy, float shear_dx, float shear_dy, int r0, int r1, int c)
|
|
{
|
|
point_fill(img, x0 + dx, y0 + dy + fast_floorf((dx * shear_dy) / shear_dx), r0, r1, c);
|
|
}
|
|
|
|
// https://scratch.mit.edu/projects/50039326/
|
|
static void scratch_draw_line(image_t *img, int x0, int y0, int dx, int dy0, int dy1, float shear_dx, float shear_dy, int c)
|
|
{
|
|
int y = y0 + fast_floorf((dx * shear_dy) / shear_dx);
|
|
yLine(img, x0 + dx, y + dy0, y + dy1, c);
|
|
}
|
|
|
|
// https://scratch.mit.edu/projects/50039326/
|
|
static void scratch_draw_sheared_ellipse(image_t *img, int x0, int y0, int width, int height, bool filled, float shear_dx, float shear_dy, int c, int thickness)
|
|
{
|
|
int thickness0 = (thickness - 0) / 2;
|
|
int thickness1 = (thickness - 1) / 2;
|
|
if (((thickness > 0) || filled) && (shear_dx != 0)) {
|
|
int a_squared = width * width;
|
|
int four_a_squared = a_squared * 4;
|
|
int b_squared = height * height;
|
|
int four_b_squared = b_squared * 4;
|
|
|
|
int x = 0;
|
|
int y = height;
|
|
int sigma = (2 * b_squared) + (a_squared * (1 - (2 * height)));
|
|
|
|
while ((b_squared * x) <= (a_squared * y)) {
|
|
if (filled) {
|
|
scratch_draw_line(img, x0, y0, x, -y, y, shear_dx, shear_dy, c);
|
|
scratch_draw_line(img, x0, y0, -x, -y, y, shear_dx, shear_dy, c);
|
|
} else {
|
|
scratch_draw_pixel(img, x0, y0, x, y, shear_dx, shear_dy, -thickness0, thickness1, c);
|
|
scratch_draw_pixel(img, x0, y0, -x, y, shear_dx, shear_dy, -thickness0, thickness1, c);
|
|
scratch_draw_pixel(img, x0, y0, x, -y, shear_dx, shear_dy, -thickness0, thickness1, c);
|
|
scratch_draw_pixel(img, x0, y0, -x, -y, shear_dx, shear_dy, -thickness0, thickness1, c);
|
|
}
|
|
|
|
if (sigma >= 0) {
|
|
sigma += four_a_squared * (1 - y);
|
|
y -= 1;
|
|
}
|
|
|
|
sigma += b_squared * ((4 * x) + 6);
|
|
x += 1;
|
|
}
|
|
|
|
x = width;
|
|
y = 0;
|
|
sigma = (2 * a_squared) + (b_squared * (1 - (2 * width)));
|
|
|
|
while ((a_squared * y) <= (b_squared * x)) {
|
|
if (filled) {
|
|
scratch_draw_line(img, x0, y0, x, -y, y, shear_dx, shear_dy, c);
|
|
scratch_draw_line(img, x0, y0, -x, -y, y, shear_dx, shear_dy, c);
|
|
} else {
|
|
scratch_draw_pixel(img, x0, y0, x, y, shear_dx, shear_dy, -thickness0, thickness1, c);
|
|
scratch_draw_pixel(img, x0, y0, -x, y, shear_dx, shear_dy, -thickness0, thickness1, c);
|
|
scratch_draw_pixel(img, x0, y0, x, -y, shear_dx, shear_dy, -thickness0, thickness1, c);
|
|
scratch_draw_pixel(img, x0, y0, -x, -y, shear_dx, shear_dy, -thickness0, thickness1, c);
|
|
}
|
|
|
|
if (sigma >= 0) {
|
|
sigma += four_b_squared * (1 - x);
|
|
x -= 1;
|
|
}
|
|
|
|
sigma += a_squared * ((4 * y) + 6);
|
|
y += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://scratch.mit.edu/projects/50039326/
|
|
static void scratch_draw_rotated_ellipse(image_t *img, int x, int y, int x_axis, int y_axis, int rotation, bool filled, int c, int thickness)
|
|
{
|
|
if ((x_axis > 0) && (y_axis > 0)) {
|
|
if ((x_axis == y_axis) || (rotation == 0)) {
|
|
scratch_draw_sheared_ellipse(img, x, y, x_axis / 2, y_axis / 2, filled, 1, 0, c, thickness);
|
|
} else if (rotation == 90) {
|
|
scratch_draw_sheared_ellipse(img, x, y, y_axis / 2, x_axis / 2, filled, 1, 0, c, thickness);
|
|
} else {
|
|
|
|
// Avoid rotations above 90.
|
|
if (rotation > 90) {
|
|
rotation -= 90;
|
|
int temp = x_axis;
|
|
x_axis = y_axis;
|
|
y_axis = temp;
|
|
}
|
|
|
|
// Avoid rotations above 45.
|
|
if (rotation > 45) {
|
|
rotation -= 90;
|
|
int temp = x_axis;
|
|
x_axis = y_axis;
|
|
y_axis = temp;
|
|
}
|
|
|
|
float theta = fast_atanf(IM_DIV(y_axis, x_axis) * (-tanf(IM_DEG2RAD(rotation))));
|
|
float shear_dx = (x_axis * cosf(theta) * cosf(IM_DEG2RAD(rotation))) - (y_axis * sinf(theta) * sinf(IM_DEG2RAD(rotation)));
|
|
float shear_dy = (x_axis * cosf(theta) * sinf(IM_DEG2RAD(rotation))) + (y_axis * sinf(theta) * cosf(IM_DEG2RAD(rotation)));
|
|
float shear_x_axis = fast_fabsf(shear_dx);
|
|
float shear_y_axis = IM_DIV((y_axis * x_axis), shear_x_axis);
|
|
scratch_draw_sheared_ellipse(img, x, y, fast_floorf(shear_x_axis / 2), fast_floorf(shear_y_axis / 2), filled, shear_dx, shear_dy, c, thickness);
|
|
}
|
|
}
|
|
}
|
|
|
|
void imlib_draw_ellipse(image_t *img, int cx, int cy, int rx, int ry, int rotation, int c, int thickness, bool fill)
|
|
{
|
|
int r = rotation % 180;
|
|
if (r < 0) r += 180;
|
|
|
|
scratch_draw_rotated_ellipse(img, cx, cy, rx * 2, ry * 2, r, fill, c, thickness);
|
|
}
|
|
|
|
// char rotation == 0, 90, 180, 360, etc.
|
|
// string rotation == 0, 90, 180, 360, etc.
|
|
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_vflip)
|
|
{
|
|
char_rotation %= 360;
|
|
if (char_rotation < 0) char_rotation += 360;
|
|
char_rotation = (char_rotation / 90) * 90;
|
|
|
|
string_rotation %= 360;
|
|
if (string_rotation < 0) string_rotation += 360;
|
|
string_rotation = (string_rotation / 90) * 90;
|
|
|
|
bool char_swap_w_h = (char_rotation == 90) || (char_rotation == 270);
|
|
bool char_upsidedown = (char_rotation == 180) || (char_rotation == 270);
|
|
|
|
if (string_hmirror) x_off -= fast_floorf(font[0].w * scale) - 1;
|
|
if (string_vflip) y_off -= fast_floorf(font[0].h * scale) - 1;
|
|
|
|
int org_x_off = x_off;
|
|
int org_y_off = y_off;
|
|
const int anchor = x_off;
|
|
|
|
for(char ch, last = '\0'; (ch = *str); str++, last = ch) {
|
|
|
|
if ((last == '\r') && (ch == '\n')) { // handle "\r\n" strings
|
|
continue;
|
|
}
|
|
|
|
if ((ch == '\n') || (ch == '\r')) { // handle '\n' or '\r' strings
|
|
x_off = anchor;
|
|
y_off += (string_vflip ? -1 : +1) * (fast_floorf((char_swap_w_h ? font[0].w : font[0].h) * scale) + y_spacing); // newline height == space height
|
|
continue;
|
|
}
|
|
|
|
if ((ch < ' ') || (ch > '~')) { // handle unknown characters
|
|
continue;
|
|
}
|
|
|
|
const glyph_t *g = &font[ch - ' '];
|
|
|
|
if (!mono_space) {
|
|
// Find the first pixel set and offset to that.
|
|
bool exit = false;
|
|
|
|
if (!char_swap_w_h) {
|
|
for (int x = 0, xx = g->w; x < xx; x++) {
|
|
for (int y = 0, yy = g->h; y < yy; y++) {
|
|
if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] &
|
|
(1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) {
|
|
x_off += (string_hmirror ? +1 : -1) * fast_floorf(x * scale);
|
|
exit = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (exit) break;
|
|
}
|
|
} else {
|
|
for (int y = g->h - 1; y >= 0; y--) {
|
|
for (int x = 0, xx = g->w; x < xx; x++) {
|
|
if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] &
|
|
(1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) {
|
|
x_off += (string_hmirror ? +1 : -1) * fast_floorf((g->h - 1 - y) * scale);
|
|
exit = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (exit) break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int y = 0, yy = fast_floorf(g->h * scale); y < yy; y++) {
|
|
for (int x = 0, xx = fast_floorf(g->w * scale); x < xx; x++) {
|
|
if (g->data[fast_floorf(y / scale)] & (1 << (g->w - 1 - fast_floorf(x / scale)))) {
|
|
int16_t x_tmp = x_off + (char_hmirror ? (xx - x - 1) : x), y_tmp = y_off + (char_vflip ? (yy - y - 1) : y);
|
|
point_rotate(x_tmp, y_tmp, IM_DEG2RAD(char_rotation), x_off + (xx / 2), y_off + (yy / 2), &x_tmp, &y_tmp);
|
|
point_rotate(x_tmp, y_tmp, IM_DEG2RAD(string_rotation), org_x_off, org_y_off, &x_tmp, &y_tmp);
|
|
imlib_set_pixel(img, x_tmp, y_tmp, c);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mono_space) {
|
|
x_off += (string_hmirror ? -1 : +1) * (fast_floorf((char_swap_w_h ? g->h : g->w) * scale) + x_spacing);
|
|
} else {
|
|
// Find the last pixel set and offset to that.
|
|
bool exit = false;
|
|
|
|
if (!char_swap_w_h) {
|
|
for (int x = g->w - 1; x >= 0; x--) {
|
|
for (int y = g->h - 1; y >= 0; y--) {
|
|
if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] &
|
|
(1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) {
|
|
x_off += (string_hmirror ? -1 : +1) * (fast_floorf((x + 2) * scale) + x_spacing);
|
|
exit = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (exit) break;
|
|
}
|
|
} else {
|
|
for (int y = 0, yy = g->h; y < yy; y++) {
|
|
for (int x = g->w - 1; x >= 0; x--) {
|
|
if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] &
|
|
(1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) {
|
|
x_off += (string_hmirror ? -1 : +1) * (fast_floorf(((g->h - 1 - y) + 2) * scale) + x_spacing);
|
|
exit = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (exit) break;
|
|
}
|
|
}
|
|
|
|
if (!exit) x_off += (string_hmirror ? -1 : +1) * fast_floorf(scale * 3); // space char
|
|
}
|
|
}
|
|
}
|
|
|
|
static int safe_map_pixel(int dst_bpp, int src_bpp, int pixel)
|
|
{
|
|
switch (dst_bpp) {
|
|
case IMAGE_BPP_BINARY: {
|
|
switch (src_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 0;
|
|
}
|
|
}
|
|
}
|
|
case IMAGE_BPP_GRAYSCALE: {
|
|
switch (src_bpp) {
|
|
case IMAGE_BPP_BINARY: {
|
|
return COLOR_BINARY_TO_GRAYSCALE(pixel);
|
|
}
|
|
case IMAGE_BPP_GRAYSCALE: {
|
|
return pixel;
|
|
}
|
|
case IMAGE_BPP_RGB565: {
|
|
return COLOR_RGB565_TO_GRAYSCALE(pixel);
|
|
}
|
|
default: {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
case IMAGE_BPP_RGB565: {
|
|
switch (src_bpp) {
|
|
case IMAGE_BPP_BINARY: {
|
|
return COLOR_BINARY_TO_RGB565(pixel);
|
|
}
|
|
case IMAGE_BPP_GRAYSCALE: {
|
|
return COLOR_GRAYSCALE_TO_RGB565(pixel);
|
|
}
|
|
case IMAGE_BPP_RGB565: {
|
|
return pixel;
|
|
}
|
|
default: {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
default: {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
// 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
|
|
|
|
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;
|
|
|
|
// 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;
|
|
|
|
// 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;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case IMAGE_BPP_GRAYSCALE: {
|
|
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;
|
|
|
|
// 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);
|
|
|
|
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;
|
|
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 = (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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case IMAGE_BPP_RGB565: {
|
|
// 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
|
|
|
|
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;
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef IMLIB_ENABLE_FLOOD_FILL
|
|
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)
|
|
{
|
|
if ((0 <= x) && (x < img->w) && (0 <= y) && (y < img->h)) {
|
|
image_t out;
|
|
out.w = img->w;
|
|
out.h = img->h;
|
|
out.bpp = IMAGE_BPP_BINARY;
|
|
out.data = fb_alloc0(image_size(&out), FB_ALLOC_NO_HINT);
|
|
|
|
if (mask) {
|
|
for (int y = 0, yy = out.h; y < yy; y++) {
|
|
uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&out, y);
|
|
for (int x = 0, xx = out.w; x < xx; x++) {
|
|
if (image_get_mask_pixel(mask, x, y)) IMAGE_SET_BINARY_PIXEL_FAST(row_ptr, x);
|
|
}
|
|
}
|
|
}
|
|
|
|
int color_seed_threshold = 0;
|
|
int color_floating_threshold = 0;
|
|
|
|
switch(img->bpp) {
|
|
case IMAGE_BPP_BINARY: {
|
|
color_seed_threshold = fast_floorf(seed_threshold * COLOR_BINARY_MAX);
|
|
color_floating_threshold = fast_floorf(floating_threshold * COLOR_BINARY_MAX);
|
|
break;
|
|
}
|
|
case IMAGE_BPP_GRAYSCALE: {
|
|
color_seed_threshold = fast_floorf(seed_threshold * COLOR_GRAYSCALE_MAX);
|
|
color_floating_threshold = fast_floorf(floating_threshold * COLOR_GRAYSCALE_MAX);
|
|
break;
|
|
}
|
|
case IMAGE_BPP_RGB565: {
|
|
color_seed_threshold = COLOR_R5_G6_B5_TO_RGB565(fast_floorf(seed_threshold * COLOR_R5_MAX),
|
|
fast_floorf(seed_threshold * COLOR_G6_MAX),
|
|
fast_floorf(seed_threshold * COLOR_B5_MAX));
|
|
color_floating_threshold = COLOR_R5_G6_B5_TO_RGB565(fast_floorf(floating_threshold * COLOR_R5_MAX),
|
|
fast_floorf(floating_threshold * COLOR_G6_MAX),
|
|
fast_floorf(floating_threshold * COLOR_B5_MAX));
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
imlib_flood_fill_int(&out, img, x, y, color_seed_threshold, color_floating_threshold, NULL, NULL);
|
|
|
|
switch(img->bpp) {
|
|
case IMAGE_BPP_BINARY: {
|
|
for (int y = 0, yy = out.h; y < yy; y++) {
|
|
uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y);
|
|
uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&out, y);
|
|
for (int x = 0, xx = out.w; x < xx; x++) {
|
|
if (IMAGE_GET_BINARY_PIXEL_FAST(out_row_ptr, x) ^ invert) {
|
|
IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, c);
|
|
} else if (clear_background) {
|
|
IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, 0);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case IMAGE_BPP_GRAYSCALE: {
|
|
for (int y = 0, yy = out.h; y < yy; y++) {
|
|
uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y);
|
|
uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&out, y);
|
|
for (int x = 0, xx = out.w; x < xx; x++) {
|
|
if (IMAGE_GET_BINARY_PIXEL_FAST(out_row_ptr, x) ^ invert) {
|
|
IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, c);
|
|
} else if (clear_background) {
|
|
IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, 0);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case IMAGE_BPP_RGB565: {
|
|
for (int y = 0, yy = out.h; y < yy; y++) {
|
|
uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y);
|
|
uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&out, y);
|
|
for (int x = 0, xx = out.w; x < xx; x++) {
|
|
if (IMAGE_GET_BINARY_PIXEL_FAST(out_row_ptr, x) ^ invert) {
|
|
IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, c);
|
|
} else if (clear_background) {
|
|
IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, 0);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
fb_free();
|
|
}
|
|
}
|
|
#endif // IMLIB_ENABLE_FLOOD_FILL
|