diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index f9ef4ad13c..834aa56aa0 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -1852,7 +1852,7 @@ msgstr "" msgid "Read-only filesystem" msgstr "" -#: shared-module/displayio/Bitmap.c +#: shared-module/bitmaptools/__init__.c shared-module/displayio/Bitmap.c msgid "Read-only object" msgstr "" @@ -2683,6 +2683,10 @@ msgstr "" msgid "circle can only be registered in one parent" msgstr "" +#: shared-bindings/bitmaptools/__init__.c +msgid "clip point must be (x,y) tuple" +msgstr "" + #: shared-bindings/msgpack/ExtType.c msgid "code outside range 0~127" msgstr "" @@ -3682,6 +3686,7 @@ msgstr "" #: ports/esp32s2/boards/targett_module_clip_wrover/mpconfigboard.h #: ports/esp32s2/boards/unexpectedmaker_feathers2/mpconfigboard.h #: ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/mpconfigboard.h +#: ports/esp32s2/boards/unexpectedmaker_tinys2/mpconfigboard.h msgid "pressing boot button at start up.\n" msgstr "" @@ -3830,7 +3835,7 @@ msgstr "" msgid "sosfilt requires iterable arguments" msgstr "" -#: shared-bindings/displayio/Bitmap.c +#: shared-bindings/bitmaptools/__init__.c shared-bindings/displayio/Bitmap.c msgid "source palette too large" msgstr "" diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 943c99f937..bb177c5d07 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -139,6 +139,9 @@ endif ifeq ($(CIRCUITPY_BITBANG_APA102),1) SRC_PATTERNS += bitbangio/SPI% endif +ifeq ($(CIRCUITPY_BITMAPTOOLS),1) +SRC_PATTERNS += bitmaptools/% +endif ifeq ($(CIRCUITPY_BITOPS),1) SRC_PATTERNS += bitops/% endif @@ -472,6 +475,7 @@ SRC_SHARED_MODULE_ALL = \ bitbangio/OneWire.c \ bitbangio/SPI.c \ bitbangio/__init__.c \ + bitmaptools/__init__.c \ bitops/__init__.c \ board/__init__.c \ adafruit_bus_device/__init__.c \ diff --git a/py/circuitpy_mpconfig.h b/py/circuitpy_mpconfig.h index cbe668289b..3eda3b0049 100644 --- a/py/circuitpy_mpconfig.h +++ b/py/circuitpy_mpconfig.h @@ -306,6 +306,13 @@ extern const struct _mp_obj_module_t bitbangio_module; #define BITBANGIO_MODULE #endif +#if CIRCUITPY_BITMAPTOOLS +#define BITMAPTOOLS_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_bitmaptools), (mp_obj_t)&bitmaptools_module }, +extern const struct _mp_obj_module_t bitmaptools_module; +#else +#define BITMAPTOOLS_MODULE +#endif + #if CIRCUITPY_BITOPS extern const struct _mp_obj_module_t bitops_module; #define BITOPS_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_bitops),(mp_obj_t)&bitops_module }, @@ -313,7 +320,6 @@ extern const struct _mp_obj_module_t bitops_module; #define BITOPS_MODULE #endif - #if CIRCUITPY_BLEIO #define BLEIO_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR__bleio), (mp_obj_t)&bleio_module }, extern const struct _mp_obj_module_t bleio_module; @@ -835,6 +841,7 @@ extern const struct _mp_obj_module_t msgpack_module; AUDIOPWMIO_MODULE \ BINASCII_MODULE \ BITBANGIO_MODULE \ + BITMAPTOOLS_MODULE \ BITOPS_MODULE \ BLEIO_MODULE \ BOARD_MODULE \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index ad63866360..d071aff050 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -95,6 +95,9 @@ CFLAGS += -DCIRCUITPY_BITBANG_APA102=$(CIRCUITPY_BITBANG_APA102) CIRCUITPY_BITBANGIO ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_BITBANGIO=$(CIRCUITPY_BITBANGIO) +CIRCUITPY_BITMAPTOOLS ?= $(CIRCUITPY_FULL_BUILD) +CFLAGS += -DCIRCUITPY_BITMAPTOOLS=$(CIRCUITPY_BITMAPTOOLS) + CIRCUITPY_BITOPS ?= 0 CFLAGS += -DCIRCUITPY_BITOPS=$(CIRCUITPY_BITOPS) diff --git a/shared-bindings/bitmaptools/__init__.c b/shared-bindings/bitmaptools/__init__.c new file mode 100644 index 0000000000..7f7e16bcb6 --- /dev/null +++ b/shared-bindings/bitmaptools/__init__.c @@ -0,0 +1,261 @@ +/* + * 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/displayio/Bitmap.h" +#include "shared-bindings/bitmaptools/__init__.h" + +#include + +#include "py/obj.h" +#include "py/runtime.h" + +STATIC int16_t validate_point(mp_obj_t point, int16_t default_value) { + // Checks if point is None and returns default_value, otherwise decodes integer value + if ( point == mp_const_none ) { + return default_value; + } + return mp_obj_get_int(point); +} + +STATIC void extract_tuple(mp_obj_t xy_tuple, int16_t *x, int16_t *y, int16_t x_default, int16_t y_default) { + // Helper function for rotozoom + // Extract x,y values from a tuple or default if None + if ( xy_tuple == mp_const_none ) { + *x = x_default; + *y = y_default; + } else if ( !MP_OBJ_IS_OBJ(xy_tuple) ) { + mp_raise_ValueError(translate("clip point must be (x,y) tuple")); + } else { + mp_obj_t* items; + mp_obj_get_array_fixed_n(xy_tuple, 2, &items); + *x = mp_obj_get_int(items[0]); + *y = mp_obj_get_int(items[1]); + } +} + +STATIC void validate_clip_region(displayio_bitmap_t *bitmap, mp_obj_t clip0_tuple, int16_t *clip0_x, int16_t *clip0_y, + mp_obj_t clip1_tuple, int16_t *clip1_x, int16_t *clip1_y) { + // Helper function for rotozoom + // 1. Extract the clip x,y points from the two clip tuples + // 2. Rearrange values such that clip0_ < clip1_ + // 3. Constrain the clip points to within the bitmap + + extract_tuple(clip0_tuple, clip0_x, clip0_y, 0, 0); + extract_tuple(clip1_tuple, clip1_x, clip1_y, bitmap->width, bitmap->height); + + // Ensure the value for clip0 is less than clip1 (for both x and y) + if ( *clip0_x > *clip1_x ) { + int16_t temp_value = *clip0_x; // swap values + *clip0_x = *clip1_x; + *clip1_x = temp_value; + } + if ( *clip0_y > *clip1_y ) { + int16_t temp_value = *clip0_y; // swap values + *clip0_y = *clip1_y; + *clip1_y = temp_value; + } + + // Constrain the clip window to within the bitmap boundaries + if (*clip0_x < 0) { + *clip0_x = 0; + } + if (*clip0_y < 0) { + *clip0_y = 0; + } + if (*clip0_x > bitmap->width) { + *clip0_x = bitmap->width; + } + if (*clip0_y > bitmap->height) { + *clip0_y = bitmap->height; + } + if (*clip1_x < 0) { + *clip1_x = 0; + } + if (*clip1_y < 0) { + *clip1_y = 0; + } + if (*clip1_x > bitmap->width) { + *clip1_x = bitmap->width; + } + if (*clip1_y > bitmap->height) { + *clip1_y = bitmap->height; + } + +} + + +//| +//| def rotozoom(dest_bitmap: Bitmap, ox: int, oy: int, +//| dest_clip0: Tuple[int, int], +//| dest_clip1: Tuple[int, int], +//| source_bitmap: Bitmap, +//| px: int, py: int, +//| source_clip0: Tuple[int, int], +//| source_clip1: Tuple[int, int], +//| angle: float, +//| scale: float, +//| skip_index: int) -> None: +//| """Inserts the source bitmap region into the destination bitmap with rotation (angle), scale +//| and clipping (both on source and destination bitmaps). +//| +//| :param bitmap dest_bitmap: The bitmap that will be copied into +//| :param int ox: Horizontal pixel location in destination bitmap where source bitmap +//| point (px,py) is placed +//| :param int oy: Vertical pixel location in destination bitmap where source bitmap +//| point (px,py) is placed +//| :param dest_clip0: First corner of rectangular destination clipping +//| region that constrains region of writing into destination bitmap +//| :type dest_clip0: Tuple[int,int] +//| :param dest_clip1: second corner of rectangular destination clipping +//| region that constrains region of writing into destination bitmap +//| :type dest_clip1: Tuple[int,int] +//| :param bitmap source_bitmap: Source bitmap that contains the graphical region to be copied +//| :param int px: Horizontal pixel location in source bitmap that is placed into the +//| destination bitmap at (ox,oy) +//| :param int py: Vertical pixel location in source bitmap that is placed into the +//| destination bitmap at (ox,oy) +//| :param source_clip0: First corner of rectangular destination clipping +//| region that constrains region of writing into destination bitmap +//| :type source_clip0: Tuple[int,int] +//| :param source_clip1: second corner of rectangular destination clipping +//| region that constrains region of writing into destination bitmap +//| :type source_clip1: Tuple[int,int] +//| :param float angle: angle of rotation, in radians (positive is clockwise direction) +//| :param float scale: scaling factor +//| :param int skip_index: bitmap palette index in the source that will not be copied, +//| set to None to copy all pixels""" +//| ... +//| +STATIC mp_obj_t bitmaptools_obj_rotozoom(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args){ + enum {ARG_dest_bitmap, ARG_ox, ARG_oy, ARG_dest_clip0, ARG_dest_clip1, + ARG_source_bitmap, ARG_px, ARG_py, + ARG_source_clip0, ARG_source_clip1, + ARG_angle, ARG_scale, ARG_skip_index}; + + static const mp_arg_t allowed_args[] = { + {MP_QSTR_dest_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ}, + {MP_QSTR_ox, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, // None convert to destination->width / 2 + {MP_QSTR_oy, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, // None convert to destination->height / 2 + {MP_QSTR_dest_clip0, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + {MP_QSTR_dest_clip1, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + + {MP_QSTR_source_bitmap, MP_ARG_REQUIRED | MP_ARG_OBJ}, + {MP_QSTR_px, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, // None convert to source->width / 2 + {MP_QSTR_py, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, // None convert to source->height / 2 + {MP_QSTR_source_clip0, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + {MP_QSTR_source_clip1, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + + {MP_QSTR_angle, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, // None convert to 0.0 + {MP_QSTR_scale, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, // None convert to 1.0 + {MP_QSTR_skip_index, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj=mp_const_none} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + displayio_bitmap_t *destination = MP_OBJ_TO_PTR(args[ARG_dest_bitmap].u_obj); // the destination bitmap + + displayio_bitmap_t *source = MP_OBJ_TO_PTR(args[ARG_source_bitmap].u_obj); // the source bitmap + + // ensure that the destination bitmap has at least as many `bits_per_value` as the source + if (destination->bits_per_value < source->bits_per_value) { + mp_raise_ValueError(translate("source palette too large")); + } + + // Confirm the destination location target (ox,oy); if None, default to bitmap midpoint + int16_t ox, oy; + ox = validate_point(args[ARG_ox].u_obj, destination->width / 2); + oy = validate_point(args[ARG_oy].u_obj, destination->height / 2); + + // Confirm the source location target (px,py); if None, default to bitmap midpoint + int16_t px, py; + px = validate_point(args[ARG_px].u_obj, source->width / 2); + py = validate_point(args[ARG_py].u_obj, source->height / 2); + + // Validate the clipping regions for the destination bitmap + int16_t dest_clip0_x, dest_clip0_y, dest_clip1_x, dest_clip1_y; + + validate_clip_region(destination, args[ARG_dest_clip0].u_obj, &dest_clip0_x, &dest_clip0_y, + args[ARG_dest_clip1].u_obj, &dest_clip1_x, &dest_clip1_y); + + // Validate the clipping regions for the source bitmap + int16_t source_clip0_x, source_clip0_y, source_clip1_x, source_clip1_y; + + validate_clip_region(source, args[ARG_source_clip0].u_obj, &source_clip0_x, &source_clip0_y, + args[ARG_source_clip1].u_obj, &source_clip1_x, &source_clip1_y); + + // Confirm the angle value + float angle=0.0; + if ( args[ARG_angle].u_obj != mp_const_none ) { + angle = mp_obj_get_float(args[ARG_angle].u_obj); + } + + // Confirm the scale value + float scale=1.0; + if ( args[ARG_scale].u_obj != mp_const_none ) { + scale = mp_obj_get_float(args[ARG_scale].u_obj); + } + if (scale < 0) { // ensure scale >= 0 + scale = 1.0; + } + + uint32_t skip_index; + bool skip_index_none; // Flag whether input skip_value was None + if (args[ARG_skip_index].u_obj == mp_const_none ) { + skip_index = 0; + skip_index_none = true; + } else { + skip_index = mp_obj_get_int(args[ARG_skip_index].u_obj); + skip_index_none = false; + } + + common_hal_bitmaptools_rotozoom(destination, ox, oy, + dest_clip0_x, dest_clip0_y, + dest_clip1_x, dest_clip1_y, + source, px, py, + source_clip0_x, source_clip0_y, + source_clip1_x, source_clip1_y, + angle, + scale, + skip_index, skip_index_none); + + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(bitmaptools_rotozoom_obj, 0, bitmaptools_obj_rotozoom); +// requires at least 2 arguments (destination bitmap and source bitmap) + + +STATIC const mp_rom_map_elem_t bitmaptools_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR_rotozoom), MP_ROM_PTR(&bitmaptools_rotozoom_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(bitmaptools_module_globals, bitmaptools_module_globals_table); + + +const mp_obj_module_t bitmaptools_module = { + .base = {&mp_type_module }, + .globals = (mp_obj_dict_t*)&bitmaptools_module_globals, +}; diff --git a/shared-bindings/bitmaptools/__init__.h b/shared-bindings/bitmaptools/__init__.h new file mode 100644 index 0000000000..e2bb6938bc --- /dev/null +++ b/shared-bindings/bitmaptools/__init__.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H +#define MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H + +#include "py/obj.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); + +#endif // MICROPY_INCLUDED_SHARED_BINDINGS_BITMAPTOOLS__INIT__H diff --git a/shared-module/bitmaptools/__init__.c b/shared-module/bitmaptools/__init__.c new file mode 100644 index 0000000000..7dc4024ef4 --- /dev/null +++ b/shared-module/bitmaptools/__init__.c @@ -0,0 +1,174 @@ +/* + * 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/displayio/Bitmap.h" + +#include "py/runtime.h" + +#include "math.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 + // # + // # * + // # * #### `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 + // # */ + + + if (self->read_only) { + mp_raise_RuntimeError(translate("Read-only object")); + } + + 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; + + 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; + } +}