summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHosted Weblate <hosted@weblate.org>2021-03-02 03:24:17 +0100
committerHosted Weblate <hosted@weblate.org>2021-03-02 03:24:17 +0100
commitef628c452132c720e9485accdf7187286a9b792f (patch)
tree3a2ba83db5c9a43c8ad4c99490789c696e936336
parentc940e112a0a80fa1f9cba41a7f5c4303cf7da42d (diff)
parentd0eab5c5615277d10eac2547c61924ff4754f0e5 (diff)
Merge remote-tracking branch 'origin/main' into main
-rw-r--r--locale/circuitpython.pot8
-rw-r--r--ports/atmel-samd/boards/bdmicro_vina_d21/mpconfigboard.mk1
-rwxr-xr-xports/atmel-samd/boards/circuitbrains_basic_m0/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/circuitplayground_express_crickit/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/circuitplayground_express_displayio/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/dynossat_edu_eps/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/feather_m0_express/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/feather_m0_express_crickit/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/feather_m0_rfm69/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/feather_m0_rfm9x/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/itsybitsy_m0_express/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/kicksat-sprite/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/metro_m0_express/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/pycubed/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/pycubed_mram/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/qtpy_m0_haxpress/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/robohatmm1_m4/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/serpente/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/snekboard/mpconfigboard.mk1
-rwxr-xr-xports/atmel-samd/boards/sparkfun_lumidrive/mpconfigboard.mk1
-rwxr-xr-xports/atmel-samd/boards/sparkfun_redboard_turbo/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/stackrduino_m0_pro/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/stringcar_m0_express/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/ugame10/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/winterbloom_big_honking_button/mpconfigboard.mk1
-rw-r--r--ports/atmel-samd/boards/winterbloom_sol/mpconfigboard.mk1
-rw-r--r--ports/cxd56/boards/spresense/mpconfigboard.mk2
-rw-r--r--ports/nrf/boards/pca10100/mpconfigboard.mk1
-rw-r--r--ports/nrf/boards/simmel/mpconfigboard.mk1
-rw-r--r--ports/stm/boards/espruino_pico/mpconfigboard.mk1
-rw-r--r--py/circuitpy_defns.mk4
-rw-r--r--py/circuitpy_mpconfig.h9
-rw-r--r--py/circuitpy_mpconfig.mk4
-rw-r--r--shared-bindings/bitmaptools/__init__.c256
-rw-r--r--shared-bindings/bitmaptools/__init__.h42
-rw-r--r--shared-module/bitmaptools/__init__.c174
39 files changed, 526 insertions, 4 deletions
diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot
index b50e63b75..20eb26e63 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 cff5a32d1..9c1d61ec7 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 2ace30fb5..6ea6a7211 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 0b150a5fb..e8993d44d 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 6d240686d..446d1177f 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 2a97636de..1f9b98bec 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 303df650e..2d5234f82 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 d1a6fc7ae..d780336a2 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 309563ff4..c3938345d 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 6ea21ed82..b544a3680 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 76a6be2e3..e307ab4f6 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 3078544fc..7a80eea9b 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 0c0d6053e..b5d22bbbf 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 249265151..ea010ed2e 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 03f633e6d..bda2ff05a 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 a82362b8d..09aef7d92 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 3bf42d705..46df12c4f 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 c653585ea..cf982cb8f 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 fd6e25f6c..470e4d9dc 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 1f2c83316..e024736c9 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 889490450..72c98867e 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 2e15bc042..29579904a 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 5b7f93328..6db5596ba 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 734be2d14..fd1520af1 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 68031e4a1..0f43ab800 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 7eda151f3..f3afa61a8 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 640d421e8..f7214704c 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 ab2b861c0..5062200e1 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 fcefaee9b..e8f94a290 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 80c6e4b69..966fa023f 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 fbb9987c2..74080a1ed 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 52743d340..283e3d169 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 6d45769f2..d6118adf8 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 943c99f93..bb177c5d0 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 cbe668289..3eda3b004 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 ad6386636..a5b0ed8a4 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 000000000..cf48d12dc
--- /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 <stdint.h>
+
+#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 000000000..e2bb6938b
--- /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 000000000..7dc4024ef
--- /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 <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
+ // # */
+
+
+ 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;
+ }
+}