circuitpython/shared-module/bitmaptools/__init__.c
Jeff Epler f5fd42c393 displayio: Move bitmap read-only checking to displayio_bitmap_set_dirty_area
This is a modest code savings, but more importantly it reduces
boilerplate in bitmap-modifying routines.

Callers need only ensure they call displayio_bitmap_set_dirty_area in
advance of the bitmap modifications they perform.

(note that this assumes that no bitmap operation can enter background
tasks. If an operation COULD enter background tasks, it MUST re-dirty
the area it touches when it exits, simply by a fresh call to
set_dirty_area with the same area as before)
2021-03-18 09:20:56 -05:00

466 lines
15 KiB
C

/*
* This file is part of the Micro Python project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Kevin Matocha
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "shared-bindings/bitmaptools/__init__.h"
#include "shared-bindings/displayio/Bitmap.h"
#include "shared-module/displayio/Bitmap.h"
#include "py/runtime.h"
#include "py/mperrno.h"
#include "math.h"
#include "stdlib.h"
void common_hal_bitmaptools_rotozoom(displayio_bitmap_t *self, int16_t ox, int16_t oy,
int16_t dest_clip0_x, int16_t dest_clip0_y,
int16_t dest_clip1_x, int16_t dest_clip1_y,
displayio_bitmap_t *source, int16_t px, int16_t py,
int16_t source_clip0_x, int16_t source_clip0_y,
int16_t source_clip1_x, int16_t source_clip1_y,
float angle,
float scale,
uint32_t skip_index, bool skip_index_none) {
// Copies region from source to the destination bitmap, including rotation,
// scaling and clipping of either the source or destination regions
//
// *self: destination bitmap
// ox: the (ox, oy) destination point where the source (px,py) point is placed
// oy:
// dest_clip0: (x,y) is the corner of the clip window on the destination bitmap
// dest_clip1: (x,y) is the other corner of the clip window of the destination bitmap
// *source: the source bitmap
// px: the (px, py) point of rotation of the source bitmap
// py:
// source_clip0: (x,y) is the corner of the clip window on the source bitmap
// source_clip1: (x,y) is the other of the clip window on the source bitmap
// angle: angle of rotation in radians, positive is clockwise
// scale: scale factor
// skip_index: color index that should be ignored (and not copied over)
// skip_index_none: if skip_index_none is True, then all color indexes should be copied
// (that is, no color indexes should be skipped)
// Copy complete "source" bitmap into "self" bitmap at location x,y in the "self"
// Add a boolean to determine if all values are copied, or only if non-zero
// If skip_value is encountered in the source bitmap, it will not be copied.
// If skip_value is `None`, then all pixels are copied.
// # Credit from https://github.com/wernsey/bitmap
// # MIT License from
// # * Copyright (c) 2017 Werner Stoop <wstoop@gmail.com>
// #
// # *
// # * #### `void bm_rotate_blit(Bitmap *dst, int ox, int oy, Bitmap *src, int px, int py, double angle, double scale);`
// # *
// # * Rotates a source bitmap `src` around a pivot point `px,py` and blits it onto a destination bitmap `dst`.
// # *
// # * The bitmap is positioned such that the point `px,py` on the source is at the offset `ox,oy` on the destination.
// # *
// # * The `angle` is clockwise, in radians. The bitmap is also scaled by the factor `scale`.
// #
// # void bm_rotate_blit(Bitmap *dst, int ox, int oy, Bitmap *src, int px, int py, double angle, double scale);
// # /*
// # Reference:
// # "Fast Bitmap Rotation and Scaling" By Steven Mortimer, Dr Dobbs' Journal, July 01, 2001
// # http://www.drdobbs.com/architecture-and-design/fast-bitmap-rotation-and-scaling/184416337
// # See also http://www.efg2.com/Lab/ImageProcessing/RotateScanline.htm
// # */
int16_t x,y;
int16_t minx = dest_clip1_x;
int16_t miny = dest_clip1_y;
int16_t maxx = dest_clip0_x;
int16_t maxy = dest_clip0_y;
float sinAngle = sinf(angle);
float cosAngle = cosf(angle);
float dx, dy;
/* Compute the position of where each corner on the source bitmap
will be on the destination to get a bounding box for scanning */
dx = -cosAngle * px * scale + sinAngle * py * scale + ox;
dy = -sinAngle * px * scale - cosAngle * py * scale + oy;
if (dx < minx) {
minx = (int16_t)dx;
}
if (dx > maxx) {
maxx = (int16_t)dx;
}
if (dy < miny) {
miny = (int16_t)dy;
}
if (dy > maxy) {
maxy = (int16_t)dy;
}
dx = cosAngle * (source->width - px) * scale + sinAngle * py * scale + ox;
dy = sinAngle * (source->width - px) * scale - cosAngle * py * scale + oy;
if (dx < minx) {
minx = (int16_t)dx;
}
if (dx > maxx) {
maxx = (int16_t)dx;
}
if (dy < miny) {
miny = (int16_t)dy;
}
if (dy > maxy) {
maxy = (int16_t)dy;
}
dx = cosAngle * (source->width - px) * scale - sinAngle * (source->height - py) * scale + ox;
dy = sinAngle * (source->width - px) * scale + cosAngle * (source->height - py) * scale + oy;
if (dx < minx) {
minx = (int16_t)dx;
}
if (dx > maxx) {
maxx = (int16_t)dx;
}
if (dy < miny) {
miny = (int16_t)dy;
}
if (dy > maxy) {
maxy = (int16_t)dy;
}
dx = -cosAngle * px * scale - sinAngle * (source->height - py) * scale + ox;
dy = -sinAngle * px * scale + cosAngle * (source->height - py) * scale + oy;
if (dx < minx) {
minx = (int16_t)dx;
}
if (dx > maxx) {
maxx = (int16_t)dx;
}
if (dy < miny) {
miny = (int16_t)dy;
}
if (dy > maxy) {
maxy = (int16_t)dy;
}
/* Clipping */
if (minx < dest_clip0_x) {
minx = dest_clip0_x;
}
if (maxx > dest_clip1_x - 1) {
maxx = dest_clip1_x - 1;
}
if (miny < dest_clip0_y) {
miny = dest_clip0_y;
}
if (maxy > dest_clip1_y - 1) {
maxy = dest_clip1_y - 1;
}
float dvCol = cosAngle / scale;
float duCol = sinAngle / scale;
float duRow = dvCol;
float dvRow = -duCol;
float startu = px - (ox * dvCol + oy * duCol);
float startv = py - (ox * dvRow + oy * duRow);
float rowu = startu + miny * duCol;
float rowv = startv + miny * dvCol;
displayio_area_t dirty_area = {minx, miny, maxx, maxy};
displayio_bitmap_set_dirty_area(self, &dirty_area);
for (y = miny; y <= maxy; y++) {
float u = rowu + minx * duRow;
float v = rowv + minx * dvRow;
for (x = minx; x <= maxx; x++) {
if (u >= source_clip0_x && u < source_clip1_x && v >= source_clip0_y && v < source_clip1_y) {
uint32_t c = common_hal_displayio_bitmap_get_pixel(source, u, v);
if ((skip_index_none) || (c != skip_index)) {
common_hal_displayio_bitmap_set_pixel(self, x, y, c);
}
}
u += duRow;
v += dvRow;
}
rowu += duCol;
rowv += dvCol;
}
}
int16_t constrain(int16_t input, int16_t min, int16_t max) {
// constrain the input between the min and max values
if (input < min) {
return min;
}
if (input > max) {
return max;
}
return input;
}
void common_hal_bitmaptools_fill_region(displayio_bitmap_t *destination,
int16_t x1, int16_t y1,
int16_t x2, int16_t y2,
uint32_t value) {
// writes the value (a bitmap color index) into a bitmap in the specified rectangular region
//
// input checks should ensure that x1 < x2 and y1 < y2 and are within the bitmap region
displayio_area_t area = { x1, y1, x2, y2 };
displayio_area_canon(&area);
displayio_area_t bitmap_area = { 0, 0, destination->width, destination->height };
displayio_area_compute_overlap(&area, &bitmap_area, &area);
// update the dirty rectangle
displayio_bitmap_set_dirty_area(destination, &area);
int16_t x, y;
for (x = area.x1; x < area.x2; x++) {
for (y = area.y1; y < area.y2; y++) {
displayio_bitmap_write_pixel(destination, x, y, value);
}
}
}
void common_hal_bitmaptools_draw_line(displayio_bitmap_t *destination,
int16_t x0, int16_t y0,
int16_t x1, int16_t y1,
uint32_t value) {
//
// adapted from Adafruit_CircuitPython_Display_Shapes.Polygon._line
//
// update the dirty rectangle
int16_t xbb0, xbb1, ybb0, ybb1;
if (x0 < x1) {
xbb0 = x0;
xbb1 = x1 + 1;
} else {
xbb0 = x1;
xbb1 = x0 + 1;
}
if (y0 < y1) {
ybb0 = y0;
ybb1 = y1 + 1;
} else {
ybb0 = y1;
ybb1 = y0 + 1;
}
displayio_area_t area = { xbb0, ybb0, xbb1, ybb1 };
displayio_area_t bitmap_area = { 0, 0, destination->width, destination->height };
displayio_area_compute_overlap(&area, &bitmap_area, &area);
displayio_bitmap_set_dirty_area(destination, &area);
int16_t temp, x, y;
if (x0 == x1) { // vertical line
if (y0 > y1) { // ensure y1 > y0
temp = y0;
y0 = y1;
y1 = temp;
}
for (y = y0; y < (y1 + 1); y++) { // write a horizontal line
displayio_bitmap_write_pixel(destination, x0, y, value);
}
} else if (y0 == y1) { // horizontal line
if (x0 > x1) { // ensure y1 > y0
temp = x0;
x0 = x1;
x1 = temp;
}
for (x = x0; x < (x1 + 1); x++) { // write a horizontal line
displayio_bitmap_write_pixel(destination, x, y0, value);
}
} else {
bool steep;
steep = (abs(y1 - y0) > abs(x1 - x0));
if (steep) { // flip x0<->y0 and x1<->y1
temp = x0;
x0 = y0;
y0 = temp;
temp = x1;
x1 = y1;
y1 = temp;
}
if (x0 > x1) { // flip x0<->x1 and y0<->y1
temp = x0;
x0 = x1;
x1 = temp;
temp = y0;
y0 = y1;
y1 = temp;
}
int16_t dx, dy, ystep;
dx = x1 - x0;
dy = abs(y1 - y0);
float err = dx / 2;
if (y0 < y1) {
ystep = 1;
} else {
ystep = -1;
}
for (x = x0; x < (x1 + 1); x++) {
if (steep) {
displayio_bitmap_write_pixel(destination, y0, x, value);
} else {
displayio_bitmap_write_pixel(destination, x, y0, value);
}
err -= dy;
if (err < 0) {
y0 += ystep;
err += dx;
}
}
}
}
void common_hal_bitmaptools_arrayblit(displayio_bitmap_t *self, void *data, int element_size, int x1, int y1, int x2, int y2, bool skip_specified, uint32_t skip_value) {
uint32_t mask = (1 << common_hal_displayio_bitmap_get_bits_per_value(self)) - 1;
for (int y = y1; y < y2; y++) {
for (int x = x1; x < x2; x++) {
uint32_t value;
switch (element_size) {
default:
case 1:
value = *(uint8_t *)data;
data = (void *)((uint8_t *)data + 1);
break;
case 2:
value = *(uint16_t *)data;
data = (void *)((uint16_t *)data + 1);
break;
case 4:
value = *(uint32_t *)data;
data = (void *)((uint32_t *)data + 1);
break;
}
if (!skip_specified || value != skip_value) {
displayio_bitmap_write_pixel(self, x, y, value & mask);
}
}
}
displayio_area_t area = { x1, y1, x2, y2 };
displayio_bitmap_set_dirty_area(self, &area);
}
void common_hal_bitmaptools_readinto(displayio_bitmap_t *self, pyb_file_obj_t *file, int element_size, int bits_per_pixel, bool reverse_pixels_in_element, bool swap_bytes) {
uint32_t mask = (1 << common_hal_displayio_bitmap_get_bits_per_value(self)) - 1;
displayio_area_t a = {0, 0, self->width, self->height};
displayio_bitmap_set_dirty_area(self, &a);
size_t elements_per_row = (self->width * bits_per_pixel + element_size * 8 - 1) / (element_size * 8);
size_t rowsize = element_size * elements_per_row;
size_t rowsize_in_u32 = (rowsize + sizeof(uint32_t) - 1) / sizeof(uint32_t);
size_t rowsize_in_u16 = (rowsize + sizeof(uint16_t) - 1) / sizeof(uint16_t);
for (int y = 0; y < self->height; y++) {
uint32_t rowdata32[rowsize_in_u32];
uint16_t *rowdata16 = (uint16_t *)rowdata32;
uint8_t *rowdata8 = (uint8_t *)rowdata32;
UINT bytes_read = 0;
if (f_read(&file->fp, rowdata32, rowsize, &bytes_read) != FR_OK || bytes_read != rowsize) {
mp_raise_OSError(MP_EIO);
}
if (swap_bytes) {
switch (element_size) {
case 2:
for (size_t i = 0; i < rowsize_in_u16; i++) {
rowdata16[i] = __builtin_bswap16(rowdata16[i]);
}
break;
case 4:
for (size_t i = 0; i < rowsize_in_u32; i++) {
rowdata32[i] = __builtin_bswap32(rowdata32[i]);
}
default:
break;
}
}
for (int x = 0; x < self->width; x++) {
int value = 0;
switch (bits_per_pixel) {
case 1: {
int byte_offset = x / 8;
int bit_offset = reverse_pixels_in_element ? (7 - x % 8) : x % 8;
value = (rowdata8[byte_offset] >> bit_offset) & 1;
break;
}
case 2: {
int byte_offset = x / 4;
int bit_offset = 2 * (reverse_pixels_in_element ? (3 - x % 4) : x % 4);
value = (rowdata8[byte_offset] >> bit_offset) & 3;
break;
}
case 4: {
int byte_offset = x / 2;
int bit_offset = 4 * (reverse_pixels_in_element ? (1 - x % 2) : x % 2);
value = (rowdata8[byte_offset] >> bit_offset) & 7;
break;
}
case 8:
value = rowdata8[x];
break;
case 16:
value = rowdata16[x];
break;
case 24:
value = (rowdata8[x * 3] << 16) | (rowdata8[x * 3 + 1] << 8) | (rowdata8[x * 3 + 2] << 8);
break;
case 32:
value = rowdata32[x];
break;
}
displayio_bitmap_write_pixel(self, x, y, value & mask);
}
}
}