diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index b50e63b759..20eb26e63a 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -1887,7 +1887,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 "" @@ -2716,6 +2716,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 "" @@ -3865,7 +3869,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/ports/atmel-samd/boards/bdmicro_vina_d21/mpconfigboard.mk b/ports/atmel-samd/boards/bdmicro_vina_d21/mpconfigboard.mk index cff5a32d1c..9c1d61ec7a 100644 --- a/ports/atmel-samd/boards/bdmicro_vina_d21/mpconfigboard.mk +++ b/ports/atmel-samd/boards/bdmicro_vina_d21/mpconfigboard.mk @@ -12,6 +12,7 @@ EXTERNAL_FLASH_DEVICES = "MX25L51245G","GD25S512MD" LONGINT_IMPL = MPZ CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_COUNTIO = 0 CIRCUITPY_I2CPERIPHERAL = 0 CIRCUITPY_MSGPACK = 0 diff --git a/ports/atmel-samd/boards/circuitbrains_basic_m0/mpconfigboard.mk b/ports/atmel-samd/boards/circuitbrains_basic_m0/mpconfigboard.mk index 2ace30fb53..6ea6a7211a 100755 --- a/ports/atmel-samd/boards/circuitbrains_basic_m0/mpconfigboard.mk +++ b/ports/atmel-samd/boards/circuitbrains_basic_m0/mpconfigboard.mk @@ -12,6 +12,7 @@ EXTERNAL_FLASH_DEVICES = "W25Q32JV_IQ" LONGINT_IMPL = MPZ CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_COUNTIO = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_I2CPERIPHERAL = 0 diff --git a/ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.mk b/ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.mk index 0b150a5fbe..e8993d44d1 100644 --- a/ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.mk +++ b/ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.mk @@ -12,6 +12,7 @@ EXTERNAL_FLASH_DEVICES = "S25FL216K, GD25Q16C" LONGINT_IMPL = MPZ # Make room for frozen libs. +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_DISPLAYIO = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_I2CPERIPHERAL = 0 diff --git a/ports/atmel-samd/boards/circuitplayground_express_crickit/mpconfigboard.mk b/ports/atmel-samd/boards/circuitplayground_express_crickit/mpconfigboard.mk index 6d240686d7..446d1177ff 100644 --- a/ports/atmel-samd/boards/circuitplayground_express_crickit/mpconfigboard.mk +++ b/ports/atmel-samd/boards/circuitplayground_express_crickit/mpconfigboard.mk @@ -13,6 +13,7 @@ EXTERNAL_FLASH_DEVICES = "S25FL216K, GD25Q16C" # Turn off features and optimizations for Crickit build to make room for additional frozen libs. LONGINT_IMPL = NONE CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_DISPLAYIO = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_I2CPERIPHERAL = 0 diff --git a/ports/atmel-samd/boards/circuitplayground_express_displayio/mpconfigboard.mk b/ports/atmel-samd/boards/circuitplayground_express_displayio/mpconfigboard.mk index 2a97636ded..1f9b98bec0 100644 --- a/ports/atmel-samd/boards/circuitplayground_express_displayio/mpconfigboard.mk +++ b/ports/atmel-samd/boards/circuitplayground_express_displayio/mpconfigboard.mk @@ -13,6 +13,7 @@ EXTERNAL_FLASH_DEVICES = "S25FL216K, GD25Q16C" # Turn off features and optimizations for Crickit build to make room for additional frozen libs. LONGINT_IMPL = NONE CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_COUNTIO = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_I2CPERIPHERAL = 0 diff --git a/ports/atmel-samd/boards/dynossat_edu_eps/mpconfigboard.mk b/ports/atmel-samd/boards/dynossat_edu_eps/mpconfigboard.mk index 303df650eb..2d5234f821 100644 --- a/ports/atmel-samd/boards/dynossat_edu_eps/mpconfigboard.mk +++ b/ports/atmel-samd/boards/dynossat_edu_eps/mpconfigboard.mk @@ -14,6 +14,7 @@ LONGINT_IMPL = MPZ CIRCUITPY_FULLBUILD = 0 CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_COUNTIO = 0 CIRCUITPY_I2CPERIPHERAL = 1 diff --git a/ports/atmel-samd/boards/feather_m0_express/mpconfigboard.mk b/ports/atmel-samd/boards/feather_m0_express/mpconfigboard.mk index d1a6fc7ae5..d780336a26 100644 --- a/ports/atmel-samd/boards/feather_m0_express/mpconfigboard.mk +++ b/ports/atmel-samd/boards/feather_m0_express/mpconfigboard.mk @@ -12,6 +12,7 @@ EXTERNAL_FLASH_DEVICES = "S25FL216K, GD25Q16C" LONGINT_IMPL = MPZ CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_COUNTIO = 0 CIRCUITPY_I2CPERIPHERAL = 0 diff --git a/ports/atmel-samd/boards/feather_m0_express_crickit/mpconfigboard.mk b/ports/atmel-samd/boards/feather_m0_express_crickit/mpconfigboard.mk index 309563ff49..c3938345d5 100644 --- a/ports/atmel-samd/boards/feather_m0_express_crickit/mpconfigboard.mk +++ b/ports/atmel-samd/boards/feather_m0_express_crickit/mpconfigboard.mk @@ -13,6 +13,7 @@ LONGINT_IMPL = MPZ # Make space for frozen libs CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_DISPLAYIO = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_I2CPERIPHERAL = 0 diff --git a/ports/atmel-samd/boards/feather_m0_rfm69/mpconfigboard.mk b/ports/atmel-samd/boards/feather_m0_rfm69/mpconfigboard.mk index 6ea21ed82e..b544a3680a 100644 --- a/ports/atmel-samd/boards/feather_m0_rfm69/mpconfigboard.mk +++ b/ports/atmel-samd/boards/feather_m0_rfm69/mpconfigboard.mk @@ -13,6 +13,7 @@ CIRCUITPY_FULL_BUILD = 0 # A number of modules are removed for RFM69 to make room for frozen libraries. # Many I/O functions are not available. CIRCUITPY_ANALOGIO = 1 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_PULSEIO = 0 CIRCUITPY_NEOPIXEL_WRITE = 1 CIRCUITPY_ROTARYIO = 0 diff --git a/ports/atmel-samd/boards/feather_m0_rfm9x/mpconfigboard.mk b/ports/atmel-samd/boards/feather_m0_rfm9x/mpconfigboard.mk index 76a6be2e34..e307ab4f6b 100644 --- a/ports/atmel-samd/boards/feather_m0_rfm9x/mpconfigboard.mk +++ b/ports/atmel-samd/boards/feather_m0_rfm9x/mpconfigboard.mk @@ -14,6 +14,7 @@ CIRCUITPY_FULL_BUILD = 0 # A number of modules are removed for RFM9x to make room for frozen libraries. # Many I/O functions are not available. CIRCUITPY_ANALOGIO = 1 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_PULSEIO = 0 CIRCUITPY_NEOPIXEL_WRITE = 1 CIRCUITPY_ROTARYIO = 0 diff --git a/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.mk b/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.mk index 3078544fcb..7a80eea9b3 100644 --- a/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.mk +++ b/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.mk @@ -12,6 +12,7 @@ EXTERNAL_FLASH_DEVICES = "S25FL064L" LONGINT_IMPL = MPZ CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_COUNTIO = 0 CIRCUITPY_I2CPERIPHERAL = 0 diff --git a/ports/atmel-samd/boards/itsybitsy_m0_express/mpconfigboard.mk b/ports/atmel-samd/boards/itsybitsy_m0_express/mpconfigboard.mk index 0c0d6053e4..b5d22bbbfb 100644 --- a/ports/atmel-samd/boards/itsybitsy_m0_express/mpconfigboard.mk +++ b/ports/atmel-samd/boards/itsybitsy_m0_express/mpconfigboard.mk @@ -15,6 +15,7 @@ CIRCUITPY_BITBANG_APA102 = 1 CIRCUITPY_AUDIOBUSIO = 0 CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_COUNTIO = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_GAMEPAD = 0 diff --git a/ports/atmel-samd/boards/kicksat-sprite/mpconfigboard.mk b/ports/atmel-samd/boards/kicksat-sprite/mpconfigboard.mk index 2492651516..ea010ed2ee 100644 --- a/ports/atmel-samd/boards/kicksat-sprite/mpconfigboard.mk +++ b/ports/atmel-samd/boards/kicksat-sprite/mpconfigboard.mk @@ -12,6 +12,7 @@ LONGINT_IMPL = MPZ # Not needed. CIRCUITPY_AUDIOBUSIO = 0 CIRCUITPY_AUDIOMP3 = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_BLEIO_HCI = 0 CIRCUITPY_DISPLAYIO = 0 CIRCUITPY_FRAMEBUFFERIO = 0 diff --git a/ports/atmel-samd/boards/metro_m0_express/mpconfigboard.mk b/ports/atmel-samd/boards/metro_m0_express/mpconfigboard.mk index 03f633e6d8..bda2ff05a7 100644 --- a/ports/atmel-samd/boards/metro_m0_express/mpconfigboard.mk +++ b/ports/atmel-samd/boards/metro_m0_express/mpconfigboard.mk @@ -12,6 +12,7 @@ EXTERNAL_FLASH_DEVICES = "S25FL216K, GD25Q16C" LONGINT_IMPL = MPZ CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_COUNTIO = 0 CIRCUITPY_I2CPERIPHERAL = 0 CIRCUITPY_MSGPACK = 0 diff --git a/ports/atmel-samd/boards/pycubed/mpconfigboard.mk b/ports/atmel-samd/boards/pycubed/mpconfigboard.mk index a82362b8d2..09aef7d92e 100644 --- a/ports/atmel-samd/boards/pycubed/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pycubed/mpconfigboard.mk @@ -15,6 +15,7 @@ CIRCUITPY_DRIVE_LABEL = "PYCUBED" # Not needed. CIRCUITPY_AUDIOBUSIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_DISPLAYIO = 0 CIRCUITPY_FRAMEBUFFERIO = 0 CIRCUITPY_GAMEPAD = 0 diff --git a/ports/atmel-samd/boards/pycubed_mram/mpconfigboard.mk b/ports/atmel-samd/boards/pycubed_mram/mpconfigboard.mk index 3bf42d7054..46df12c4f2 100644 --- a/ports/atmel-samd/boards/pycubed_mram/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pycubed_mram/mpconfigboard.mk @@ -15,6 +15,7 @@ CIRCUITPY_DRIVE_LABEL = "PYCUBED" # Not needed. CIRCUITPY_AUDIOBUSIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_DISPLAYIO = 0 CIRCUITPY_FRAMEBUFFERIO = 0 CIRCUITPY_GAMEPAD = 0 diff --git a/ports/atmel-samd/boards/qtpy_m0_haxpress/mpconfigboard.mk b/ports/atmel-samd/boards/qtpy_m0_haxpress/mpconfigboard.mk index c653585ea8..cf982cb8fc 100644 --- a/ports/atmel-samd/boards/qtpy_m0_haxpress/mpconfigboard.mk +++ b/ports/atmel-samd/boards/qtpy_m0_haxpress/mpconfigboard.mk @@ -14,6 +14,7 @@ EXTERNAL_FLASH_DEVICES = GD25Q16C CIRCUITPY_AUDIOBUSIO = 0 CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_COUNTIO = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_I2CPERIPHERAL = 0 diff --git a/ports/atmel-samd/boards/robohatmm1_m4/mpconfigboard.mk b/ports/atmel-samd/boards/robohatmm1_m4/mpconfigboard.mk index fd6e25f6cb..470e4d9dc4 100644 --- a/ports/atmel-samd/boards/robohatmm1_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/robohatmm1_m4/mpconfigboard.mk @@ -16,6 +16,7 @@ LONGINT_IMPL = MPZ # No I2S on SAMD51G CIRCUITPY_AUDIOBUSIO = 0 # Make room for more stuff +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_DISPLAYIO = 0 CIRCUITPY_FRAMEBUFFERIO = 0 CIRCUITPY_FREQUENCYIO = 0 diff --git a/ports/atmel-samd/boards/serpente/mpconfigboard.mk b/ports/atmel-samd/boards/serpente/mpconfigboard.mk index 1f2c83316c..e024736c95 100644 --- a/ports/atmel-samd/boards/serpente/mpconfigboard.mk +++ b/ports/atmel-samd/boards/serpente/mpconfigboard.mk @@ -12,6 +12,7 @@ EXTERNAL_FLASH_DEVICES = GD25Q32C LONGINT_IMPL = NONE CIRCUITPY_AUDIOBUSIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_GAMEPAD = 0 CIRCUITPY_BUSDEVICE = 0 diff --git a/ports/atmel-samd/boards/snekboard/mpconfigboard.mk b/ports/atmel-samd/boards/snekboard/mpconfigboard.mk index 889490450f..72c98867e2 100644 --- a/ports/atmel-samd/boards/snekboard/mpconfigboard.mk +++ b/ports/atmel-samd/boards/snekboard/mpconfigboard.mk @@ -12,6 +12,7 @@ EXTERNAL_FLASH_DEVICES = "W25Q16JV_IQ" LONGINT_IMPL = MPZ CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_GAMEPAD = 0 CIRCUITPY_I2CPERIPHERAL = 0 CIRCUITPY_MSGPACK = 0 diff --git a/ports/atmel-samd/boards/sparkfun_lumidrive/mpconfigboard.mk b/ports/atmel-samd/boards/sparkfun_lumidrive/mpconfigboard.mk index 2e15bc042a..29579904a2 100755 --- a/ports/atmel-samd/boards/sparkfun_lumidrive/mpconfigboard.mk +++ b/ports/atmel-samd/boards/sparkfun_lumidrive/mpconfigboard.mk @@ -13,6 +13,7 @@ LONGINT_IMPL = MPZ CIRCUITPY_AUDIOIO = 0 CIRCUITPY_AUDIOBUSIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_VECTORIO = 0 CIRCUITPY_BUSDEVICE = 0 diff --git a/ports/atmel-samd/boards/sparkfun_redboard_turbo/mpconfigboard.mk b/ports/atmel-samd/boards/sparkfun_redboard_turbo/mpconfigboard.mk index 5b7f93328d..6db5596ba3 100755 --- a/ports/atmel-samd/boards/sparkfun_redboard_turbo/mpconfigboard.mk +++ b/ports/atmel-samd/boards/sparkfun_redboard_turbo/mpconfigboard.mk @@ -12,6 +12,7 @@ EXTERNAL_FLASH_DEVICES = "W25Q32FV" LONGINT_IMPL = MPZ CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_COUNTIO = 0 CIRCUITPY_GAMEPAD = 0 CIRCUITPY_I2CPERIPHERAL = 0 diff --git a/ports/atmel-samd/boards/stackrduino_m0_pro/mpconfigboard.mk b/ports/atmel-samd/boards/stackrduino_m0_pro/mpconfigboard.mk index 734be2d145..fd1520af15 100644 --- a/ports/atmel-samd/boards/stackrduino_m0_pro/mpconfigboard.mk +++ b/ports/atmel-samd/boards/stackrduino_m0_pro/mpconfigboard.mk @@ -13,6 +13,7 @@ LONGINT_IMPL = MPZ CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_COUNTIO = 0 CIRCUITPY_I2CPERIPHERAL = 0 diff --git a/ports/atmel-samd/boards/stringcar_m0_express/mpconfigboard.mk b/ports/atmel-samd/boards/stringcar_m0_express/mpconfigboard.mk index 68031e4a18..0f43ab8002 100644 --- a/ports/atmel-samd/boards/stringcar_m0_express/mpconfigboard.mk +++ b/ports/atmel-samd/boards/stringcar_m0_express/mpconfigboard.mk @@ -16,6 +16,7 @@ CIRCUITPY_BITBANG_APA102 = 1 CIRCUITPY_AUDIOBUSIO = 0 CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_GAMEPAD = 0 CIRCUITPY_I2CPERIPHERAL = 0 CIRCUITPY_RTC = 0 diff --git a/ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.mk b/ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.mk index 7eda151f36..f3afa61a8f 100644 --- a/ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.mk +++ b/ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.mk @@ -13,6 +13,7 @@ LONGINT_IMPL = MPZ CIRCUITPY_AUDIOBUSIO = 0 CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_COUNTIO = 0 CIRCUITPY_RTC = 0 CIRCUITPY_FREQUENCYIO = 0 diff --git a/ports/atmel-samd/boards/ugame10/mpconfigboard.mk b/ports/atmel-samd/boards/ugame10/mpconfigboard.mk index 640d421e81..f7214704ce 100644 --- a/ports/atmel-samd/boards/ugame10/mpconfigboard.mk +++ b/ports/atmel-samd/boards/ugame10/mpconfigboard.mk @@ -20,6 +20,7 @@ CIRCUITPY_DISPLAYIO = 1 CIRCUITPY_AUDIOBUSIO = 0 CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_I2CPERIPHERAL = 0 CIRCUITPY_NEOPIXEL_WRITE = 0 diff --git a/ports/atmel-samd/boards/winterbloom_big_honking_button/mpconfigboard.mk b/ports/atmel-samd/boards/winterbloom_big_honking_button/mpconfigboard.mk index ab2b861c03..5062200e1f 100644 --- a/ports/atmel-samd/boards/winterbloom_big_honking_button/mpconfigboard.mk +++ b/ports/atmel-samd/boards/winterbloom_big_honking_button/mpconfigboard.mk @@ -17,6 +17,7 @@ CIRCUITPY_AUDIOIO = 1 # Disable modules that are unusable on this special-purpose board. CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_FRAMEBUFFERIO = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_AUDIOBUSIO = 0 diff --git a/ports/atmel-samd/boards/winterbloom_sol/mpconfigboard.mk b/ports/atmel-samd/boards/winterbloom_sol/mpconfigboard.mk index fcefaee9b8..e8f94a2902 100644 --- a/ports/atmel-samd/boards/winterbloom_sol/mpconfigboard.mk +++ b/ports/atmel-samd/boards/winterbloom_sol/mpconfigboard.mk @@ -18,6 +18,7 @@ LONGINT_IMPL = MPZ # Disable modules that are unusable on this special-purpose board. CIRCUITPY_AUDIOBUSIO = 0 CIRCUITPY_AUDIOIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_BLEIO_HCI = 0 CIRCUITPY_DISPLAYIO = 0 CIRCUITPY_FRAMEBUFFERIO = 0 diff --git a/ports/cxd56/boards/spresense/mpconfigboard.mk b/ports/cxd56/boards/spresense/mpconfigboard.mk index 80c6e4b692..966fa023f0 100644 --- a/ports/cxd56/boards/spresense/mpconfigboard.mk +++ b/ports/cxd56/boards/spresense/mpconfigboard.mk @@ -4,5 +4,5 @@ USB_PRODUCT = "Spresense" USB_MANUFACTURER = "Sony" INTERNAL_FLASH_FILESYSTEM = 1 - +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_MSGPACK = 0 diff --git a/ports/nrf/boards/pca10100/mpconfigboard.mk b/ports/nrf/boards/pca10100/mpconfigboard.mk index fbb9987c24..74080a1ed9 100644 --- a/ports/nrf/boards/pca10100/mpconfigboard.mk +++ b/ports/nrf/boards/pca10100/mpconfigboard.mk @@ -9,6 +9,7 @@ INTERNAL_FLASH_FILESYSTEM = 1 CIRCUITPY_AUDIOMP3 = 0 CIRCUITPY_BITBANGIO = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_BUSIO = 1 CIRCUITPY_COUNTIO = 0 CIRCUITPY_DISPLAYIO = 0 diff --git a/ports/nrf/boards/simmel/mpconfigboard.mk b/ports/nrf/boards/simmel/mpconfigboard.mk index 52743d340f..283e3d1690 100644 --- a/ports/nrf/boards/simmel/mpconfigboard.mk +++ b/ports/nrf/boards/simmel/mpconfigboard.mk @@ -13,6 +13,7 @@ INTERNAL_FLASH_FILESYSTEM = 1 CIRCUITPY_AESIO = 1 CIRCUITPY_AUDIOMP3 = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_BUSDEVICE = 0 CIRCUITPY_BUSIO = 1 CIRCUITPY_DISPLAYIO = 0 diff --git a/ports/stm/boards/espruino_pico/mpconfigboard.mk b/ports/stm/boards/espruino_pico/mpconfigboard.mk index 6d45769f2b..d6118adf88 100644 --- a/ports/stm/boards/espruino_pico/mpconfigboard.mk +++ b/ports/stm/boards/espruino_pico/mpconfigboard.mk @@ -21,6 +21,7 @@ LD_FILE = boards/STM32F401xd_fs.ld # meantime CIRCUITPY_ULAB = 0 CIRCUITPY_BUSDEVICE = 0 +CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_FRAMEBUFFERIO = 0 SUPEROPT_GC = 0 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..a5b0ed8a44 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -152,11 +152,15 @@ CFLAGS += -DCIRCUITPY_ERRNO=$(CIRCUITPY_ERRNO) CIRCUITPY_ESPIDF ?= 0 CFLAGS += -DCIRCUITPY_ESPIDF=$(CIRCUITPY_ESPIDF) +# bitmaptools and framebufferio rely on displayio ifeq ($(CIRCUITPY_DISPLAYIO),1) +CIRCUITPY_BITMAPTOOLS ?= $(CIRCUITPY_FULL_BUILD) CIRCUITPY_FRAMEBUFFERIO ?= $(CIRCUITPY_FULL_BUILD) else +CIRCUITPY_BITMAPTOOLS ?= 0 CIRCUITPY_FRAMEBUFFERIO ?= 0 endif +CFLAGS += -DCIRCUITPY_BITMAPTOOLS=$(CIRCUITPY_BITMAPTOOLS) CFLAGS += -DCIRCUITPY_FRAMEBUFFERIO=$(CIRCUITPY_FRAMEBUFFERIO) CIRCUITPY_VECTORIO ?= $(CIRCUITPY_DISPLAYIO) diff --git a/shared-bindings/bitmaptools/__init__.c b/shared-bindings/bitmaptools/__init__.c new file mode 100644 index 0000000000..cf48d12dc1 --- /dev/null +++ b/shared-bindings/bitmaptools/__init__.c @@ -0,0 +1,256 @@ +/* + * 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" + +//| """Collection of bitmap manipulation tools""" +//| + +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: displayio.Bitmap, source_bitmap: displayio.Bitmap, +//| *, +//| ox: int, oy: int, dest_clip0: Tuple[int, int], dest_clip1: Tuple[int, int], +//| 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: Destination bitmap that will be copied into +//| :param bitmap source_bitmap: Source bitmap that contains the graphical region to be copied +//| :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 Tuple[int,int] dest_clip0: First corner of rectangular destination clipping +//| region that constrains region of writing into destination bitmap +//| :param Tuple[int,int] dest_clip1: Second corner of rectangular destination clipping +//| region that constrains region of writing into destination bitmap +//| :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 Tuple[int,int] source_clip0: First corner of rectangular source clipping +//| region that constrains region of reading from the source bitmap +//| :param Tuple[int,int] source_clip1: Second corner of rectangular source clipping +//| region that constrains region of reading from the source bitmap +//| :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_source_bitmap, + ARG_ox, ARG_oy, ARG_dest_clip0, ARG_dest_clip1, + 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_source_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_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; + } +}