From 497f8323daf61c1771d5a9d03bca4b2a9f780e26 Mon Sep 17 00:00:00 2001 From: Jason Jackson Date: Sat, 30 Nov 2024 10:19:34 -0500 Subject: [PATCH 1/2] deleted libs --- src/lib/adafruit_bitmap_font/__init__.py | 0 src/lib/adafruit_bitmap_font/bdf.mpy | Bin 2315 -> 0 bytes src/lib/adafruit_bitmap_font/bdf.py | 241 ------- src/lib/adafruit_bitmap_font/bitmap_font.mpy | Bin 586 -> 0 bytes src/lib/adafruit_bitmap_font/bitmap_font.py | 70 -- src/lib/adafruit_bitmap_font/glyph_cache.mpy | Bin 491 -> 0 bytes src/lib/adafruit_bitmap_font/glyph_cache.py | 64 -- src/lib/adafruit_bitmap_font/pcf.mpy | Bin 3874 -> 0 bytes src/lib/adafruit_bitmap_font/ttf.mpy | Bin 822 -> 0 bytes src/lib/adafruit_bus_device/__init__.py | 0 src/lib/adafruit_bus_device/i2c_device.mpy | Bin 1172 -> 0 bytes src/lib/adafruit_bus_device/i2c_device.py | 195 ------ src/lib/adafruit_bus_device/spi_device.mpy | Bin 845 -> 0 bytes src/lib/adafruit_bus_device/spi_device.py | 129 ---- src/lib/adafruit_connection_manager.mpy | Bin 2867 -> 0 bytes src/lib/adafruit_display_text/__init__.mpy | Bin 4258 -> 0 bytes src/lib/adafruit_display_text/__init__.py | 480 -------------- .../adafruit_display_text/bitmap_label.mpy | Bin 3923 -> 0 bytes src/lib/adafruit_display_text/bitmap_label.py | 605 ------------------ src/lib/adafruit_display_text/label.mpy | Bin 3597 -> 0 bytes src/lib/adafruit_display_text/label.py | 455 ------------- .../adafruit_display_text/outlined_label.py | 196 ------ .../adafruit_display_text/scrolling_label.mpy | Bin 1126 -> 0 bytes .../adafruit_display_text/scrolling_label.py | 168 ----- src/lib/adafruit_display_text/text_box.py | 435 ------------- src/lib/adafruit_ds3231.mpy | Bin 1225 -> 0 bytes src/lib/adafruit_imageload/__init__.mpy | Bin 921 -> 0 bytes src/lib/adafruit_imageload/__init__.py | 105 --- src/lib/adafruit_imageload/bmp/__init__.mpy | Bin 1034 -> 0 bytes src/lib/adafruit_imageload/bmp/__init__.py | 114 ---- src/lib/adafruit_imageload/bmp/indexed.mpy | Bin 1666 -> 0 bytes src/lib/adafruit_imageload/bmp/indexed.py | 273 -------- .../bmp/negative_height_check.mpy | Bin 150 -> 0 bytes src/lib/adafruit_imageload/bmp/truecolor.mpy | Bin 1353 -> 0 bytes src/lib/adafruit_imageload/bmp/truecolor.py | 139 ---- .../adafruit_imageload/displayio_types.mpy | Bin 331 -> 0 bytes src/lib/adafruit_imageload/displayio_types.py | 36 -- src/lib/adafruit_imageload/gif.mpy | Bin 1712 -> 0 bytes src/lib/adafruit_imageload/gif.py | 177 ----- src/lib/adafruit_imageload/jpg.py | 57 -- src/lib/adafruit_imageload/png.mpy | Bin 1063 -> 0 bytes src/lib/adafruit_imageload/png.py | 192 ------ src/lib/adafruit_imageload/pnm/__init__.mpy | Bin 1211 -> 0 bytes src/lib/adafruit_imageload/pnm/__init__.py | 146 ----- src/lib/adafruit_imageload/pnm/pbm_ascii.mpy | Bin 465 -> 0 bytes src/lib/adafruit_imageload/pnm/pbm_ascii.py | 60 -- src/lib/adafruit_imageload/pnm/pbm_binary.mpy | Bin 615 -> 0 bytes src/lib/adafruit_imageload/pnm/pbm_binary.py | 82 --- .../adafruit_imageload/pnm/pgm/__init__.mpy | Bin 577 -> 0 bytes .../adafruit_imageload/pnm/pgm/__init__.py | 55 -- src/lib/adafruit_imageload/pnm/pgm/ascii.mpy | Bin 807 -> 0 bytes src/lib/adafruit_imageload/pnm/pgm/ascii.py | 79 --- src/lib/adafruit_imageload/pnm/pgm/binary.mpy | Bin 669 -> 0 bytes src/lib/adafruit_imageload/pnm/pgm/binary.py | 67 -- src/lib/adafruit_imageload/pnm/ppm_ascii.mpy | Bin 909 -> 0 bytes src/lib/adafruit_imageload/pnm/ppm_ascii.py | 106 --- src/lib/adafruit_imageload/pnm/ppm_binary.mpy | Bin 736 -> 0 bytes src/lib/adafruit_imageload/pnm/ppm_binary.py | 82 --- .../adafruit_imageload/tilegrid_inflator.mpy | Bin 1019 -> 0 bytes .../adafruit_imageload/tilegrid_inflator.py | 117 ---- src/lib/adafruit_irremote.mpy | Bin 2957 -> 0 bytes src/lib/adafruit_ntp.mpy | Bin 912 -> 0 bytes src/lib/adafruit_register/__init__.py | 0 src/lib/adafruit_register/i2c_bcd_alarm.mpy | Bin 1740 -> 0 bytes src/lib/adafruit_register/i2c_bcd_alarm.py | 210 ------ .../adafruit_register/i2c_bcd_datetime.mpy | Bin 1141 -> 0 bytes src/lib/adafruit_register/i2c_bcd_datetime.py | 122 ---- src/lib/adafruit_register/i2c_bit.mpy | Bin 801 -> 0 bytes src/lib/adafruit_register/i2c_bit.py | 92 --- src/lib/adafruit_register/i2c_bits.mpy | Bin 1053 -> 0 bytes src/lib/adafruit_register/i2c_bits.py | 122 ---- src/lib/adafruit_register/i2c_struct.mpy | Bin 1040 -> 0 bytes src/lib/adafruit_register/i2c_struct.py | 112 ---- .../adafruit_register/i2c_struct_array.mpy | Bin 1008 -> 0 bytes src/lib/adafruit_register/i2c_struct_array.py | 122 ---- src/lib/adafruit_requests.mpy | Bin 5767 -> 0 bytes src/lib/adafruit_ticks.mpy | Bin 694 -> 0 bytes src/lib/ir_rx/__init__.py | 70 -- src/lib/ir_rx/acquire.py | 108 ---- src/lib/ir_rx/mce.py | 68 -- src/lib/ir_rx/nec.py | 69 -- src/lib/ir_rx/philips.py | 123 ---- src/lib/ir_rx/print_error.py | 19 - src/lib/ir_rx/sony.py | 70 -- src/lib/ir_rx/test.py | 70 -- 85 files changed, 6302 deletions(-) delete mode 100644 src/lib/adafruit_bitmap_font/__init__.py delete mode 100644 src/lib/adafruit_bitmap_font/bdf.mpy delete mode 100644 src/lib/adafruit_bitmap_font/bdf.py delete mode 100644 src/lib/adafruit_bitmap_font/bitmap_font.mpy delete mode 100644 src/lib/adafruit_bitmap_font/bitmap_font.py delete mode 100644 src/lib/adafruit_bitmap_font/glyph_cache.mpy delete mode 100644 src/lib/adafruit_bitmap_font/glyph_cache.py delete mode 100644 src/lib/adafruit_bitmap_font/pcf.mpy delete mode 100644 src/lib/adafruit_bitmap_font/ttf.mpy delete mode 100644 src/lib/adafruit_bus_device/__init__.py delete mode 100644 src/lib/adafruit_bus_device/i2c_device.mpy delete mode 100644 src/lib/adafruit_bus_device/i2c_device.py delete mode 100644 src/lib/adafruit_bus_device/spi_device.mpy delete mode 100644 src/lib/adafruit_bus_device/spi_device.py delete mode 100644 src/lib/adafruit_connection_manager.mpy delete mode 100644 src/lib/adafruit_display_text/__init__.mpy delete mode 100644 src/lib/adafruit_display_text/__init__.py delete mode 100644 src/lib/adafruit_display_text/bitmap_label.mpy delete mode 100644 src/lib/adafruit_display_text/bitmap_label.py delete mode 100644 src/lib/adafruit_display_text/label.mpy delete mode 100644 src/lib/adafruit_display_text/label.py delete mode 100644 src/lib/adafruit_display_text/outlined_label.py delete mode 100644 src/lib/adafruit_display_text/scrolling_label.mpy delete mode 100644 src/lib/adafruit_display_text/scrolling_label.py delete mode 100644 src/lib/adafruit_display_text/text_box.py delete mode 100644 src/lib/adafruit_ds3231.mpy delete mode 100644 src/lib/adafruit_imageload/__init__.mpy delete mode 100644 src/lib/adafruit_imageload/__init__.py delete mode 100644 src/lib/adafruit_imageload/bmp/__init__.mpy delete mode 100644 src/lib/adafruit_imageload/bmp/__init__.py delete mode 100644 src/lib/adafruit_imageload/bmp/indexed.mpy delete mode 100644 src/lib/adafruit_imageload/bmp/indexed.py delete mode 100644 src/lib/adafruit_imageload/bmp/negative_height_check.mpy delete mode 100644 src/lib/adafruit_imageload/bmp/truecolor.mpy delete mode 100644 src/lib/adafruit_imageload/bmp/truecolor.py delete mode 100644 src/lib/adafruit_imageload/displayio_types.mpy delete mode 100644 src/lib/adafruit_imageload/displayio_types.py delete mode 100644 src/lib/adafruit_imageload/gif.mpy delete mode 100644 src/lib/adafruit_imageload/gif.py delete mode 100644 src/lib/adafruit_imageload/jpg.py delete mode 100644 src/lib/adafruit_imageload/png.mpy delete mode 100644 src/lib/adafruit_imageload/png.py delete mode 100644 src/lib/adafruit_imageload/pnm/__init__.mpy delete mode 100644 src/lib/adafruit_imageload/pnm/__init__.py delete mode 100644 src/lib/adafruit_imageload/pnm/pbm_ascii.mpy delete mode 100644 src/lib/adafruit_imageload/pnm/pbm_ascii.py delete mode 100644 src/lib/adafruit_imageload/pnm/pbm_binary.mpy delete mode 100644 src/lib/adafruit_imageload/pnm/pbm_binary.py delete mode 100644 src/lib/adafruit_imageload/pnm/pgm/__init__.mpy delete mode 100644 src/lib/adafruit_imageload/pnm/pgm/__init__.py delete mode 100644 src/lib/adafruit_imageload/pnm/pgm/ascii.mpy delete mode 100644 src/lib/adafruit_imageload/pnm/pgm/ascii.py delete mode 100644 src/lib/adafruit_imageload/pnm/pgm/binary.mpy delete mode 100644 src/lib/adafruit_imageload/pnm/pgm/binary.py delete mode 100644 src/lib/adafruit_imageload/pnm/ppm_ascii.mpy delete mode 100644 src/lib/adafruit_imageload/pnm/ppm_ascii.py delete mode 100644 src/lib/adafruit_imageload/pnm/ppm_binary.mpy delete mode 100644 src/lib/adafruit_imageload/pnm/ppm_binary.py delete mode 100644 src/lib/adafruit_imageload/tilegrid_inflator.mpy delete mode 100644 src/lib/adafruit_imageload/tilegrid_inflator.py delete mode 100644 src/lib/adafruit_irremote.mpy delete mode 100644 src/lib/adafruit_ntp.mpy delete mode 100644 src/lib/adafruit_register/__init__.py delete mode 100644 src/lib/adafruit_register/i2c_bcd_alarm.mpy delete mode 100644 src/lib/adafruit_register/i2c_bcd_alarm.py delete mode 100644 src/lib/adafruit_register/i2c_bcd_datetime.mpy delete mode 100644 src/lib/adafruit_register/i2c_bcd_datetime.py delete mode 100644 src/lib/adafruit_register/i2c_bit.mpy delete mode 100644 src/lib/adafruit_register/i2c_bit.py delete mode 100644 src/lib/adafruit_register/i2c_bits.mpy delete mode 100644 src/lib/adafruit_register/i2c_bits.py delete mode 100644 src/lib/adafruit_register/i2c_struct.mpy delete mode 100644 src/lib/adafruit_register/i2c_struct.py delete mode 100644 src/lib/adafruit_register/i2c_struct_array.mpy delete mode 100644 src/lib/adafruit_register/i2c_struct_array.py delete mode 100644 src/lib/adafruit_requests.mpy delete mode 100644 src/lib/adafruit_ticks.mpy delete mode 100644 src/lib/ir_rx/__init__.py delete mode 100644 src/lib/ir_rx/acquire.py delete mode 100644 src/lib/ir_rx/mce.py delete mode 100644 src/lib/ir_rx/nec.py delete mode 100644 src/lib/ir_rx/philips.py delete mode 100644 src/lib/ir_rx/print_error.py delete mode 100644 src/lib/ir_rx/sony.py delete mode 100644 src/lib/ir_rx/test.py diff --git a/src/lib/adafruit_bitmap_font/__init__.py b/src/lib/adafruit_bitmap_font/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/lib/adafruit_bitmap_font/bdf.mpy b/src/lib/adafruit_bitmap_font/bdf.mpy deleted file mode 100644 index 36a7f568c1f35e42e8ed774020f958806554fe56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2315 zcmZWp+fy6Y89%xLLdL7rdRaiW7in1}ED)%&>QIN8v&%(JxCkLY$)vJaEug6^sgg)d zFT<|Pq;&Gqhd%Z%NMh7p`x5m%!6hvuH;ggMPwaLrRC5BGN!V~imOnFOr{FCbYcZu#zhHPM$#*} zC1llJ02fs?g@m*uBYIIgic3|);_vC}=|g5L4kK6n0^p+hOyVZ&1zG++Vor!k=|rJ` z9PzxINTyR6IbOY4>U787mGh~EmH2#iIg z4A3)VVxpPCaxRz67v&_YwSzsOip*!UTXAtHB2Y>b578bao} zlV%}vLzS_14Bnw}v8EX?o`WbHnjRaRMCSUTabZFu0zPpzBuD{K(JKm}Ck%vt^>URbc>^b zZirpC%-Z~4h(heT?j0rE)<`!hgcVNDIIOfarV>q{pLI0RO)-L?zvV)7VA_ueo2@Be zplxAEx8@+)ZR_r9?-A?pTVs-Jl0~-5bda#o-!8LAN0YWIeqBIEUj(TK>GT`u7Qc~h z_M2$lZ>BrTQr&xV_l<7ywf^tW%B3!j^KiVkljqlb|)Hx-n zi`}U(bcJdTqPKv0u5$ zc6wO13wwIRvi(B%kKzx?r_h?h2GOI6fbrWEfHnkm;v_jxq1uB;jRtgO<^sl;fjDVx zvfbhASKidn_!G4AfR_ClO(Xd=mhu0v&I49!*?wO7dGDX~d`rLb_6;`0^1RdM@m>_1 z6V6vq3O-O~AC)!0$9T9VHz=xJ&>pZ)h}oO^=@JJ$*(F+zIH&flY0@t8H5W zU6--UaO&~w`=1Jx)!3-HZ0BOvTV~1cej!fnhw8~|E^|P4Zy~(zu6qZ@A}py_+AYof zO2K_v7;>;ebOiL$T!V%0SANI>g0w+>z<#3d?8nMaoZ9DdIz1g8Pq#WA%5Zq&==tq_B z1z;9m;E3iG!7El?;9*j%fOCiP1X27AW^c1>@A_OEhkY|Dj_gpEHZ>rAN5EkTGVL`U z9vvOM>d8q>e)B*O@UPY$Kojbq2}6^Q@%vS)9<1pH)s`5K%1mpSX)7}h{FmF>)dn~e z@Sm!>MGKpHf;`m)FDd@hEko_P&n!Z#l#ys*0G_whkB z;4B2Bx5R~7jNyp4^Aymt13f!P3M9qZIjTz!MVi6CtOgMfB;a-Rj`$x)`dI-e7Jf%yZV{X6jP7q-uhJd^=d43Up;<~ ohc+Pi8vfmn;qOu%-M&`e_nNXEDMuo2SRRo>FnNnH90*JQ1({f<{Qv*} diff --git a/src/lib/adafruit_bitmap_font/bdf.py b/src/lib/adafruit_bitmap_font/bdf.py deleted file mode 100644 index 777fadf..0000000 --- a/src/lib/adafruit_bitmap_font/bdf.py +++ /dev/null @@ -1,241 +0,0 @@ -# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_bitmap_font.bdf` -==================================================== - -Loads BDF format fonts. - -* Author(s): Scott Shawcroft - -Implementation Notes --------------------- - -**Hardware:** - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the supported boards: - https://github.com/adafruit/circuitpython/releases - -""" - -try: - from typing import Union, Optional, Tuple, Iterable - from io import FileIO - from displayio import Bitmap -except ImportError: - pass - -import gc -from fontio import Glyph -from .glyph_cache import GlyphCache - -<<<<<<< HEAD -__version__ = "2.1.1" -======= -<<<<<<< HEAD -__version__ = "2.1.3" -======= -__version__ = "2.1.1" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font.git" - - -class BDF(GlyphCache): - """Loads glyphs from a BDF file in the given bitmap_class.""" - - def __init__(self, f: FileIO, bitmap_class: Bitmap) -> None: - super().__init__() - self.file = f - self.name = f - self.file.seek(0) - self.bitmap_class = bitmap_class - line = self._readline_file() - if not line or not line.startswith("STARTFONT 2.1"): - raise ValueError("Unsupported file version") - self._verify_bounding_box() - self.point_size = None - self.x_resolution = None - self.y_resolution = None - self._ascent = None - self._descent = None - - @property - def descent(self) -> Optional[int]: - """The number of pixels below the baseline of a typical descender""" - if self._descent is None: - self.file.seek(0) - while True: - line = self.file.readline() - if not line: - break - - if line.startswith(b"FONT_DESCENT "): - self._descent = int(line.split()[1]) - break - - return self._descent - - @property - def ascent(self) -> Optional[int]: - """The number of pixels above the baseline of a typical ascender""" - if self._ascent is None: - self.file.seek(0) - while True: - line = self._readline_file() - if not line: - break - - if line.startswith("FONT_ASCENT "): - self._ascent = int(line.split()[1]) - break - - return self._ascent - - def _verify_bounding_box(self) -> None: - """Private function to verify FOUNTBOUNDINGBOX parameter - This function will parse the first 10 lines of the font source - file to verify the value or raise an exception in case is not found - """ - self.file.seek(0) - # Normally information about the FONT is in the first four lines. - # Exception is when font file have a comment. Comments are three lines - # 10 lines is a safe bet - for _ in range(11): - line = self._readline_file() - while line.startswith("COMMENT "): - line = self._readline_file() - if line.startswith("FONTBOUNDINGBOX "): - _, x, y, x_offset, y_offset = line.split() - self._boundingbox = (int(x), int(y), int(x_offset), int(y_offset)) - - try: - self._boundingbox - except AttributeError as error: - raise RuntimeError( - "Source file does not have the FOUNTBOUNDINGBOX parameter" - ) from error - - def _readline_file(self) -> str: - line = self.file.readline() - return str(line, "utf-8") - - def get_bounding_box(self) -> Tuple[int, int, int, int]: - """Return the maximum glyph size as a 4-tuple of: width, height, x_offset, y_offset""" - return self._boundingbox - - def load_glyphs(self, code_points: Union[int, str, Iterable[int]]) -> None: - # pylint: disable=too-many-statements,too-many-branches,too-many-nested-blocks,too-many-locals - metadata = True - character = False - code_point = None - bytes_per_row = 1 - desired_character = False - current_info = {} - current_y = 0 - rounded_x = 1 - if isinstance(code_points, int): - remaining = set() - remaining.add(code_points) - elif isinstance(code_points, str): - remaining = set(ord(c) for c in code_points) - elif isinstance(code_points, set): - remaining = code_points - else: - remaining = set(code_points) - for code_point in remaining.copy(): - if code_point in self._glyphs and self._glyphs[code_point]: - remaining.remove(code_point) - if not remaining: - return - - x, _, _, _ = self._boundingbox - - self.file.seek(0) - while True: - line = self.file.readline() - if not line: - break - if line.startswith(b"CHARS "): - metadata = False - elif line.startswith(b"SIZE"): - _, self.point_size, self.x_resolution, self.y_resolution = line.split() - elif line.startswith(b"COMMENT"): - pass - elif line.startswith(b"STARTCHAR"): - character = True - elif line.startswith(b"ENDCHAR"): - character = False - if desired_character: - bounds = current_info["bounds"] - shift = current_info["shift"] - gc.collect() - self._glyphs[code_point] = Glyph( - current_info["bitmap"], - 0, - bounds[0], - bounds[1], - bounds[2], - bounds[3], - shift[0], - shift[1], - ) - remaining.remove(code_point) - if not remaining: - return - desired_character = False - elif line.startswith(b"BBX"): - if desired_character: - _, x, y, x_offset, y_offset = line.split() - x = int(x) - y = int(y) - x_offset = int(x_offset) - y_offset = int(y_offset) - current_info["bounds"] = (x, y, x_offset, y_offset) - current_info["bitmap"] = self.bitmap_class(x, y, 2) - elif line.startswith(b"BITMAP"): - if desired_character: - rounded_x = x // 8 - if x % 8 > 0: - rounded_x += 1 - bytes_per_row = rounded_x - if bytes_per_row % 4 > 0: - bytes_per_row += 4 - bytes_per_row % 4 - current_y = 0 - elif line.startswith(b"ENCODING"): - _, code_point = line.split() - code_point = int(code_point) - if code_point in remaining: - desired_character = True - current_info = {"bitmap": None, "bounds": None, "shift": None} - elif line.startswith(b"DWIDTH"): - if desired_character: - _, shift_x, shift_y = line.split() - shift_x = int(shift_x) - shift_y = int(shift_y) - current_info["shift"] = (shift_x, shift_y) - elif line.startswith(b"SWIDTH"): - pass - elif character: - if desired_character: - bits = int(line.strip(), 16) - width = current_info["bounds"][0] - start = current_y * width - x = 0 - for i in range(rounded_x): - val = (bits >> ((rounded_x - i - 1) * 8)) & 0xFF - for j in range(7, -1, -1): - if x >= width: - break - bit = 0 - if val & (1 << j) != 0: - bit = 1 - current_info["bitmap"][start + x] = bit - x += 1 - current_y += 1 - elif metadata: - pass diff --git a/src/lib/adafruit_bitmap_font/bitmap_font.mpy b/src/lib/adafruit_bitmap_font/bitmap_font.mpy deleted file mode 100644 index fe657297b1276b08c31bdc1e3edbf79967ea6baa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmYk1U2D@&7{{NJHt8Cp%d&W;wyD>l6-Bx z#4}hHAIHRxLTe0sKcoNxnZzz94 z-T^Fh9Q1DKH>wGEEv?>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font.git" - - -def load_font( - filename: str, bitmap: Optional[Bitmap] = None -) -> Union[bdf.BDF, pcf.PCF, ttf.TTF]: - """Loads a font file. Returns None if unsupported.""" - # pylint: disable=import-outside-toplevel, redefined-outer-name, consider-using-with - if not bitmap: - import displayio - - bitmap = displayio.Bitmap - font_file = open(filename, "rb") - first_four = font_file.read(4) - if filename.endswith("bdf") and first_four == b"STAR": - from . import bdf - - return bdf.BDF(font_file, bitmap) - if filename.endswith("pcf") and first_four == b"\x01fcp": - from . import pcf - - return pcf.PCF(font_file, bitmap) - if filename.endswith("ttf") and first_four == b"\x00\x01\x00\x00": - from . import ttf - - return ttf.TTF(font_file, bitmap) - - raise ValueError("Unknown magic number %r" % first_four) diff --git a/src/lib/adafruit_bitmap_font/glyph_cache.mpy b/src/lib/adafruit_bitmap_font/glyph_cache.mpy deleted file mode 100644 index 1a401cf3bbcc2f10e6fec02235e1b2098472ddb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 491 zcmYL^QA^uk6oyYy+u9iG*EBn;wy-j%9i2-sI4^d!8Oc--7Fd;BjL-B-Oad_p>F1z# znQXA1)A|$kKgRybQcKy*;k@U(=RG`T4PNXBmg~8<`F+IcIO2D1MsL%E>p?utLdskg z`dT)HEoqcQX#(V&`?))geUSMyi;@7OPYWl=i>oMwwSa+SXouP%s81Vn5dphR`jj{U zj8oTJ2_^9PvWJ@MdC+8O9Q%yJB~tp$&nM+iN`X+C`&n9c`rCI8ZDgtE(=3e=J^}T= zfQRksNyvFNd9Uk1#KZfs#?m`|1*m^qk(yD?O5)E`9+nSImfTiJpp|Fvvv<%DZu*7# z(ioZ6EAOmd5Rp`dVt1`4qS&io#VJU2D0FMRDps8f>AFS!o|?{u@U^hJb*z(hZ0!On zUko6PBM}=m6TWkh8^88GcwVcGDrw*|15h#qlnoI#ZB_|e>k<~P4`9~Wu-Ta+$@si@ zW3wM*xU-3!LhBE#y}|xqFrT$I{+wE6osXEZeFVQB)#m(IaO}nCYNpDMrBLeX)-gCs F*FW;bhtB{2 diff --git a/src/lib/adafruit_bitmap_font/glyph_cache.py b/src/lib/adafruit_bitmap_font/glyph_cache.py deleted file mode 100644 index 1a8cc62..0000000 --- a/src/lib/adafruit_bitmap_font/glyph_cache.py +++ /dev/null @@ -1,64 +0,0 @@ -# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_bitmap_font.glyph_cache` -==================================================== - -Displays text using CircuitPython's displayio. - -* Author(s): Scott Shawcroft - -Implementation Notes --------------------- - -**Hardware:** - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the supported boards: - https://github.com/adafruit/circuitpython/releases - -""" - -try: - from typing import Union, Iterable - from fontio import Glyph -except ImportError: - pass - -import gc - -<<<<<<< HEAD -__version__ = "2.1.1" -======= -<<<<<<< HEAD -__version__ = "2.1.3" -======= -__version__ = "2.1.1" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font.git" - - -class GlyphCache: - """Caches glyphs loaded by a subclass.""" - - def __init__(self) -> None: - self._glyphs = {} - - def load_glyphs(self, code_points: Union[int, str, Iterable[int]]) -> None: - """Loads displayio.Glyph objects into the GlyphCache from the font.""" - - def get_glyph(self, code_point: int) -> Glyph: - """Returns a displayio.Glyph for the given code point or None is unsupported.""" - if code_point in self._glyphs: - return self._glyphs[code_point] - - code_points = set() - code_points.add(code_point) - self._glyphs[code_point] = None - self.load_glyphs(code_points) - gc.collect() - return self._glyphs[code_point] diff --git a/src/lib/adafruit_bitmap_font/pcf.mpy b/src/lib/adafruit_bitmap_font/pcf.mpy deleted file mode 100644 index fea304911abdd63c08d2a1c07a0f2ea732b96d46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3874 zcmaJ@T~Hg>6}~I9{0M>1!W~z&6Gv_SnY%PBLj9l6E}lQ}XJG?8)Sa|QIt}uw|PXpnHDo~>}KuJ;1F`uC%{=$k9|V*c}Y5rj8k)o zgp@;8Eec{v$b(isCkb&>e}Kz};icFFFb{;`hYCG=E2q4$ZHd=Jj4BhkR0&D5t{x+r zuQCv;3TQ?vw-KdfB99mx1YXFCQd&mMRbZ`fw8y@MJQ27iTUKuA&GP8tRSA|6S>SRVt5>UEiA@NB9WJ5q(1<$_RruS()+`x*3X4e zyBerjExr1I0IK&7%#5HypItr5<}z6+Cnq68^|F*op~5c~9QFQiAdHw)Mu_v3>VcqY zRRr$Ih=zFaQ}eR451?>n3u;{NHrETjjG$(%bkWZ3sJxeq%QKK3O}`D2ndiY3KASuv zrH~cMa265>p@-*tbJEYGTwYQS^E?>R&XMInDw&tX%xu<==mXPIS~`=>`O(g;CJLxs zb=Y2QRa~6Ow46cxeFK#C{(026VAdtj!a#xGn-iJbtRVBKFkw)OOJcEsoRrT?alRT& zXh^7vYzB1YQ6X43xX{0_o0igZv$%mIRLB@=$XZnwBK4JrkhNO647H@`zw3ykzo*{F z9p8-_No{kqDk4qWtZG4#mTjIOLx)ez$+#1nNkclrXCR^}A&Wp6OcYswSXW<0HbEsr zj)hcZt|1ZdBO7u3 zY`M~~-H2oZoM>H0HvMOlYV_8*fduKT;;Y=O)T9?}^Y>xARjZ^iE+vGyl+5E0A%l)= z{C0tpMs2p>q~#IW5>4mlve`^dmg06bqxLS~w;f5R=Iv9-X}bhnE2QmPQgHUIS!%D8 zk$pCq&*KR~8vOz7hi76ffzWT{-a%s0u2?$#z0qK=)7jfQ-WgqNb;s?Cb@1(Ahu$68 zuTVOwhSF1{Ti;P@(=j?*4O3&&GkP1zkTHd-a}yo)wmPN`a06h2t%3P*Y}7De`pG{- z!PqEqN}(EoZ?rWsjWH$C(Zn>t+RQX7)DGZNwjIn4xN8DT+f0lpR-nv)EjBY_j*Wm+ z3s8)$g*g=)ZK`M;iba@HpxD$%wK@=|V_E@O9RwgNpf-mNP#d6jM-8BMKn)H(poX`+ zi0Uwzxf-S;A`**6YM^5&F}TRi49*P3?ZvucLwvH>P~2Vg7Iz`jI}A^CFq6x6L@+j! z;3YVM*$=x8_Sd~N%o|=k)9EFdgWg)E$6LpEy!A}b+rV^t4a~5&kr`hWjTeqyvJBd$ z#Q1I{>1Nq(7whSA_C;M?VV7&fHR5!-My4FJ)P+hdHXn+ul&r77w{yj^OYspgI#J+= zlGVOivhFTfIw^;b*sI&T*NaM4_KFfXHL;1gUBK1ffAH`T-MV&dovSIC*{dfCUz~jV z)8Nin45+23+4=bTg=e8KJ?$W`f5Q<{$??ot!A&3med5-atgl7HlMRObqH@8$Y~F+M>!R{o zzyZON@hQZEJGB22vGo4#plZ3L&NlGM=iW^MO>hI zj6I_AXD5iWm!h|qEeFA3jBdwQ27N?ffDns&!?Z_KO3q>TJ{O2BRmJXLp+i{j-Kw#r za&h%#Su+{%5u0?w>gAPl&!e19T&&NrK{2IVcT7(1apGS{sp~I=vRF+B)R1@XZr;1U z(pf-X-dV-#-OG60yn@$zSMhpZc@Q{)<=+JP+KT)G+PboO`O4Kbu2wvM^1R*MEh^7E zjvmKw_mE=bx?K*}s3Qb^TZTFpkdL@s3Wh7xLIvYk)IluNm)}_dndf)Gf}M zzjzqvQyp2t>0DI;(-V)e;V}qH&6B5(HbUcTH==9nQMYb778n7%j*;3|mRKwzUJgCB z|3x=0&R*>6arTVNIszU~&sd);JTvI-a}9BwT+r@1HnPX#8g}<~IXnA%J?b`N@Ai1Q zTpo`%ImmX++xzgLXKaWK$&)U(qsQa&!ap?W5}o06p`ntSxE+XKG7IF07nL|*4AMVc zwi86@9S+74q&yH2yAUpMv;RW*4u;V6{-yHWDl9Gn>NOuOzbtQJUJ-@@u7!IV;lC0J zE(Q)QmG1-iRp8LCA4EI^odjbqAJRG4__6aaeI7l&zVuaR;q3D#*YW!F23&WWfAnc^ zR7FQV3k3o#8x`(R;HoI{StuB|c~%pCQ8|sMGA|0{mm6gbe+WBt^~%Brl_u*#kdR}L z5aOeoo#*~__E{)&^V<4}z{~fyeEV4Qtrxp?Z9VFwSSs>4_|T`Sy1K?vRb4?Sid%4< z-n#8HY1g^`aecM8eO;RcPMW1z6R0uJO_t*@w70dH>EtbrPy+u@CLY}aUo5O8^7-FF zj}+Eoeic6Mg|h{=0r-4nCj-yh&9<=2g0E)6*~*&NPtYc|RlzJQjHNz-G)hsQ)q z>rrj2UpgC$X^!8nX;83w#M~{IPuLYp0b?6j|#_j&Qaq&UThj0(X@OXGg_I3)_Ybv?zhe38#&1V{J=c zM`4V?{>@j1r-Ll_uRv?MBjUdSW_P<( diff --git a/src/lib/adafruit_bitmap_font/ttf.mpy b/src/lib/adafruit_bitmap_font/ttf.mpy deleted file mode 100644 index a70cfea9b5e6a1192879b17e16857ac5997976e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 822 zcmXX@T~8B16uq-7ODR=`Wm!I2q;$7a3aur$^szB55DGEUq=0K;$hzIqhSE0eHd4{J zB|aE`fj@IVku{6zeE#5bRe@A?OvE#8;8=iYnHnLCr>U@Y1Z*K%6E)+m}A_lxGX zR^7-~%4WcH^H+|EE(zBz!oduNw(*K&lT&{lGb!eb+gvc zP2jTG1&~~I!`K2>o>D;23tCOnO`}!^HjxH*Vvz=T5=uRtPJ<_B=#`uSdntE<;>&&} zL&pjx7s6kE#!W&K*1*L4yx}LyL?Tl+R>BjpSS%V{ zo(xNwP)H8VhGU^53L>G$iX%0M_J=$X*dMxb;0N^9##a^e&c?k8nr7dB_;|*L4;7?F z;M1X%EUus17(1QMU%r0h!^c7&=HrN;e5Ih1Q-xJePA8e8@=pp(etzm_apKjzLXDk;--rho)#CAqSG zE(e8S8;>Z65-;yPrBXAmf~-|SZ0r+8WfDhi7^3tLr6Y`k6n!(m7-I^LVe*X4*rt5dojMQ$Mrl-46jKXZ{RoL8i-k32hDR6$GoxX~ zF^ftM*J{|M%SzpKQDuqkB>SpW{y{cfG=HPZz8P?9RawoMbKkl5o^!W3uvYd|TkTfI zv^7gs?19|ICz^`$T0xz`oG}Dpzoi{x0KYXXP4BmQz`s8(-SJ8A3*vb`h zcL7hSn!6V`%Zyfd-L~X`)iNy*av<)v0o3|4DoByTq&VsUh*LjutKBy7U;xsxY?!)& z<&JL741&>j%O+j5W9rAU*0*$Uwi|lt>`GzQ1+vw}ec5^B)}>kXKibiPrNE$vu>qiE z4&|P%J^}Hd-FLy)NS05qNj>#t89jLXQx1DFw7fjP9#+Ez20(%jX|!R@YW?s(zfmK5YJr>kDsB6Td%AylM}o zvF4}){3~-KF4~M1D$cR_i<~BuJTQI`2O$gIa!~j@Dmz zVN$xYSs=fxJA8`TystjH`<7jeN#xg~N^&!Ol1OH4<30M_FY94P$!&w^&b^%Q7Zrd diff --git a/src/lib/adafruit_bus_device/i2c_device.py b/src/lib/adafruit_bus_device/i2c_device.py deleted file mode 100644 index 2b29b6e..0000000 --- a/src/lib/adafruit_bus_device/i2c_device.py +++ /dev/null @@ -1,195 +0,0 @@ -# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_bus_device.i2c_device` - I2C Bus Device -==================================================== -""" - -import time - -try: - from typing import Optional, Type - from types import TracebackType - from circuitpython_typing import ReadableBuffer, WriteableBuffer - - # Used only for type annotations. - from busio import I2C -except ImportError: - pass - - -<<<<<<< HEAD -__version__ = "5.2.8" -======= -<<<<<<< HEAD -__version__ = "5.2.10" -======= -__version__ = "5.2.8" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BusDevice.git" - - -class I2CDevice: - """ - Represents a single I2C device and manages locking the bus and the device - address. - - :param ~busio.I2C i2c: The I2C bus the device is on - :param int device_address: The 7 bit device address - :param bool probe: Probe for the device upon object creation, default is true - - .. note:: This class is **NOT** built into CircuitPython. See - :ref:`here for install instructions `. - - Example: - - .. code-block:: python - - import busio - from board import * - from adafruit_bus_device.i2c_device import I2CDevice - - with busio.I2C(SCL, SDA) as i2c: - device = I2CDevice(i2c, 0x70) - bytes_read = bytearray(4) - with device: - device.readinto(bytes_read) - # A second transaction - with device: - device.write(bytes_read) - """ - - def __init__(self, i2c: I2C, device_address: int, probe: bool = True) -> None: - self.i2c = i2c - self.device_address = device_address - - if probe: - self.__probe_for_device() - - def readinto( - self, buf: WriteableBuffer, *, start: int = 0, end: Optional[int] = None - ) -> None: - """ - Read into ``buf`` from the device. The number of bytes read will be the - length of ``buf``. - - If ``start`` or ``end`` is provided, then the buffer will be sliced - as if ``buf[start:end]``. This will not cause an allocation like - ``buf[start:end]`` will so it saves memory. - - :param ~WriteableBuffer buffer: buffer to write into - :param int start: Index to start writing at - :param int end: Index to write up to but not include; if None, use ``len(buf)`` - """ - if end is None: - end = len(buf) - self.i2c.readfrom_into(self.device_address, buf, start=start, end=end) - - def write( - self, buf: ReadableBuffer, *, start: int = 0, end: Optional[int] = None - ) -> None: - """ - Write the bytes from ``buffer`` to the device, then transmit a stop - bit. - - If ``start`` or ``end`` is provided, then the buffer will be sliced - as if ``buffer[start:end]``. This will not cause an allocation like - ``buffer[start:end]`` will so it saves memory. - - :param ~ReadableBuffer buffer: buffer containing the bytes to write - :param int start: Index to start writing from - :param int end: Index to read up to but not include; if None, use ``len(buf)`` - """ - if end is None: - end = len(buf) - self.i2c.writeto(self.device_address, buf, start=start, end=end) - - # pylint: disable-msg=too-many-arguments - def write_then_readinto( - self, - out_buffer: ReadableBuffer, - in_buffer: WriteableBuffer, - *, - out_start: int = 0, - out_end: Optional[int] = None, - in_start: int = 0, - in_end: Optional[int] = None - ) -> None: - """ - Write the bytes from ``out_buffer`` to the device, then immediately - reads into ``in_buffer`` from the device. The number of bytes read - will be the length of ``in_buffer``. - - If ``out_start`` or ``out_end`` is provided, then the output buffer - will be sliced as if ``out_buffer[out_start:out_end]``. This will - not cause an allocation like ``buffer[out_start:out_end]`` will so - it saves memory. - - If ``in_start`` or ``in_end`` is provided, then the input buffer - will be sliced as if ``in_buffer[in_start:in_end]``. This will not - cause an allocation like ``in_buffer[in_start:in_end]`` will so - it saves memory. - - :param ~ReadableBuffer out_buffer: buffer containing the bytes to write - :param ~WriteableBuffer in_buffer: buffer containing the bytes to read into - :param int out_start: Index to start writing from - :param int out_end: Index to read up to but not include; if None, use ``len(out_buffer)`` - :param int in_start: Index to start writing at - :param int in_end: Index to write up to but not include; if None, use ``len(in_buffer)`` - """ - if out_end is None: - out_end = len(out_buffer) - if in_end is None: - in_end = len(in_buffer) - - self.i2c.writeto_then_readfrom( - self.device_address, - out_buffer, - in_buffer, - out_start=out_start, - out_end=out_end, - in_start=in_start, - in_end=in_end, - ) - - # pylint: enable-msg=too-many-arguments - - def __enter__(self) -> "I2CDevice": - while not self.i2c.try_lock(): - time.sleep(0) - return self - - def __exit__( - self, - exc_type: Optional[Type[type]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], - ) -> bool: - self.i2c.unlock() - return False - - def __probe_for_device(self) -> None: - """ - Try to read a byte from an address, - if you get an OSError it means the device is not there - or that the device does not support these means of probing - """ - while not self.i2c.try_lock(): - time.sleep(0) - try: - self.i2c.writeto(self.device_address, b"") - except OSError: - # some OS's dont like writing an empty bytesting... - # Retry by reading a byte - try: - result = bytearray(1) - self.i2c.readfrom_into(self.device_address, result) - except OSError: - # pylint: disable=raise-missing-from - raise ValueError("No I2C device at address: 0x%x" % self.device_address) - # pylint: enable=raise-missing-from - finally: - self.i2c.unlock() diff --git a/src/lib/adafruit_bus_device/spi_device.mpy b/src/lib/adafruit_bus_device/spi_device.mpy deleted file mode 100644 index 7d696ae94b3eb78c5e0909713f7ab42244aa9860..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 845 zcmXw#-%b-j6vodMsC5N8-ED`}R>9JiKVloJG{mb4s{x{c3JNvRWjh_{hNZjNoke=( z774)%-(^+M7f@fo3r#ff%2zO6IxR5COn!6boB6&ub72^s6lV3hzUg=d(`ufp)#%&?le36a&7O`)H)Y@@jaS5_UJ(3(!RyJeA=sSDwi{4x4H$CoJ)4jT*GfuW; z>W;x$AlePxrJ&F~=I9zREwb&xXqcx4Od5u*xzwbD!HE0PV5FfjOS3#?bMO88mqpj4 z)CRhSnYO#`H94z(>#g`3m+GNOA#>--Z2 zJH(Shf45YFw$N8@FlM`V3kCk^hF8lIYo~DD<-)!5cF!3O;cZ`Tu?Dx8_1x1APhZLAq#8Jt3L8UrB~=Xnw{=J*amwlno{6rQ7gJTeOS`f3r-z?~2ts1V^bQ{n?hSN$8q?Zkl? zOXE>AfHRdzVG5SfU@d+4b_7LhK0^k50n0@JE5#5_RY=c4`R5z|sGO6t@x)*h z#Uy_vb2_r`0wsPNDAAKZK_3Gp_9;-L_koi55GaFzoKPR~C63Y1BEFJy*(pmrc mzf|?4{pZVxKl^CyYsdNOG0)Qev;OS+T)x^3b*%)|W%3`i)b%0& diff --git a/src/lib/adafruit_bus_device/spi_device.py b/src/lib/adafruit_bus_device/spi_device.py deleted file mode 100644 index 9f0c85b..0000000 --- a/src/lib/adafruit_bus_device/spi_device.py +++ /dev/null @@ -1,129 +0,0 @@ -# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -# pylint: disable=too-few-public-methods - -""" -`adafruit_bus_device.spi_device` - SPI Bus Device -==================================================== -""" - -import time - -try: - from typing import Optional, Type - from types import TracebackType - - # Used only for type annotations. - from busio import SPI - from digitalio import DigitalInOut -except ImportError: - pass - - -<<<<<<< HEAD -__version__ = "5.2.8" -======= -<<<<<<< HEAD -__version__ = "5.2.10" -======= -__version__ = "5.2.8" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BusDevice.git" - - -class SPIDevice: - """ - Represents a single SPI device and manages locking the bus and the device - address. - - :param ~busio.SPI spi: The SPI bus the device is on - :param ~digitalio.DigitalInOut chip_select: The chip select pin object that implements the - DigitalInOut API. - :param bool cs_active_value: Set to True if your device requires CS to be active high. - Defaults to False. - :param int baudrate: The desired SCK clock rate in Hertz. The actual clock rate may be - higher or lower due to the granularity of available clock settings (MCU dependent). - :param int polarity: The base state of the SCK clock pin (0 or 1). - :param int phase: The edge of the clock that data is captured. First (0) or second (1). - Rising or falling depends on SCK clock polarity. - :param int extra_clocks: The minimum number of clock cycles to cycle the bus after CS is high. - (Used for SD cards.) - - .. note:: This class is **NOT** built into CircuitPython. See - :ref:`here for install instructions `. - - Example: - - .. code-block:: python - - import busio - import digitalio - from board import * - from adafruit_bus_device.spi_device import SPIDevice - - with busio.SPI(SCK, MOSI, MISO) as spi_bus: - cs = digitalio.DigitalInOut(D10) - device = SPIDevice(spi_bus, cs) - bytes_read = bytearray(4) - # The object assigned to spi in the with statements below - # is the original spi_bus object. We are using the busio.SPI - # operations busio.SPI.readinto() and busio.SPI.write(). - with device as spi: - spi.readinto(bytes_read) - # A second transaction - with device as spi: - spi.write(bytes_read) - """ - - def __init__( - self, - spi: SPI, - chip_select: Optional[DigitalInOut] = None, - *, - cs_active_value: bool = False, - baudrate: int = 100000, - polarity: int = 0, - phase: int = 0, - extra_clocks: int = 0 - ) -> None: - self.spi = spi - self.baudrate = baudrate - self.polarity = polarity - self.phase = phase - self.extra_clocks = extra_clocks - self.chip_select = chip_select - self.cs_active_value = cs_active_value - if self.chip_select: - self.chip_select.switch_to_output(value=not self.cs_active_value) - - def __enter__(self) -> SPI: - while not self.spi.try_lock(): - time.sleep(0) - self.spi.configure( - baudrate=self.baudrate, polarity=self.polarity, phase=self.phase - ) - if self.chip_select: - self.chip_select.value = self.cs_active_value - return self.spi - - def __exit__( - self, - exc_type: Optional[Type[type]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], - ) -> bool: - if self.chip_select: - self.chip_select.value = not self.cs_active_value - if self.extra_clocks > 0: - buf = bytearray(1) - buf[0] = 0xFF - clocks = self.extra_clocks // 8 - if self.extra_clocks % 8 != 0: - clocks += 1 - for _ in range(clocks): - self.spi.write(buf) - self.spi.unlock() - return False diff --git a/src/lib/adafruit_connection_manager.mpy b/src/lib/adafruit_connection_manager.mpy deleted file mode 100644 index f8581221c1f07aa9fbbf7649f6cf909ac6e4581d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2867 zcmZ`(PjD008Gn*3TL@uRyX!?UDr_ZtEo>thSr!yrVx$$8Yzu2G3F4&AvR7_mQ()Q9zGo4)lA%@Ty9Fi#s*!hs2)C@k03ODZqQm5L;)CAq?{2^C>Qs)ec>=&%O$X>$d*AYa{IFk2%AbQ=I*^5^T4qAD~fX$vW+B3Y}!u1ysj zZ}MYR@tbmR!giI`diz;sd2)FE_y_LW`0c}6p8n$2;Fjn1mWbJ@ivdJUY248cssqMO zs?*a+IEb!*?M33chIM1kA^Xt-zHX}9=cJqq8s@?dVs7jZ)-&Wddf4ZpT)u)v65 z7}32W260mNo-~M%?hP44SoelG@z&_B9UJ9x+63({q})1jf*dt;Vkl-8ui*KYY<@TG z!V7o@aVlzSEOWa-`{J~1krV4>r^~a=7HKONPcs*8$d((kQDczLYM(2}UDA?}M}!kM zQ-87HV_bsOX6WG={IX6&uxQlI&fpjXix99D@ob&H>QVb=@g)O0-jveBkN2drkWvTfhZG>M zyLigDd=F0>mtj#5>u=K-}5RLA<4ve>{9hcQcI4(d%#?5d~wdV*X=`!hQLD2|UH-5WE9)4uu=Q&r+3&XgFM>+1tyB;Jri6) zyXR+ATZ+K17&Nl!#A(y~-6#|aw7>O|O)PLCuxzW}{9@9Tyn0o;pEGi~nZuU*0rY~6 zHdYqmX8)`XQRl$lBYI?TkQoFvPm@25Az)$(&j%a;oK8*vg1ZJ8ilGHa)X2R9{GV>X z1nC6**m*Gj9L!&1J8zzcW4|Z=^oMP2@|SON3va*kv;F}_`iSaXjDL9z$ z`~89b0k)D#_!s8GWv2WEKa=w_mj^NobCksSy{_nY51k$9jL9@Gml z`_t)@SMb71Hv2r?_2}8;t-76i9z3)-eZ;zmE$M%>mejWR5j%IE9NWQIBerRKYJ3&H z_~6$Ct?<3Pv^qusBKoC+3a#WVwZvi606SYWycL@ f6?miSSr0at`Q0E*GlTdvc$>m;xH3nk@oDjY0{@-1 diff --git a/src/lib/adafruit_display_text/__init__.mpy b/src/lib/adafruit_display_text/__init__.mpy deleted file mode 100644 index 254b3b6dfebb1301a25dddc8a1e0ccb016ee49d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4258 zcmZ`)T~Hg>6~4RTN5}%NR*SO1)Y(WY0picdvg$a-mX|eP0|sGaha~PIR%;|%tQ1Mu z5Z7jxBu(meI_gX&o#{hfI-O2B(-|!W{1eA+>^O1Vv}u}jlC+cb$q#+So=zXyNzYvb z0TN3y_niBkpL_0i?!DVpfu7K-s)blQb}~Da%uDfPE|ZGQNO@&C-!4f>6)>qSGlRC; zcDC(k3n5!iHa(R=HAAtKlFuuME=45Mh#Jh~lW8@ULe~CdE{|*@Q<;>4?D?5YQk_6- zcUsL4Wz+ezoK7M8Nti*9h3=1yD=8tCQ;_d$HkL7$mhx#SlblvkIpj*EV{vIBHIqr? zP}Q8LQjd_WXolb5n@Xxm4$WD}YE*IVd@ItMbZ6`Ms_(drJl|q4>^s<8Dz`JusLLtaETx=7l@KFr5)QPs6U-WZfQIwpPm3Z z)OZQ1lW-wYE)$dSjyHJZlM{(NVj)dD4o28v25n!Pji>YZ^dzcV15(P#JgQ%tX3gk2 zDW=MabXLlwlTaF8nksPsa!E`+q#Oq-rC2ICp-w8`#7QYkFmyJn$fgS?<)HnN1kJ!6Y0w7j zxqKF%LmGE-rN)}18V4qJppw#o^qsV#PE9J=7{1NeQ%=OPkokCf>ne263o-%ldcC zTGr7P*kvOtiC{Uwq4K>$DR{3GZYmRpws14)sVpa_n$APvN?eI^ zF?D7#omJY9)zZhmoeF~*IBd2!gi zJ1YM)|8jsLUC(-u>p6|GP-T8{TY0_3WvMT7m5uiLG^&ECq^y3+wyOFHS4DlLtFqqe zvW{w$%};FGQg3tFMi1MLJD&fi*fn~XI1!=jl-+MRx)Zr-0))#o5+JE+hb3rnRYzpY zo1*b+;umDii602kn6_P`yA{sg`-;`WdwuQ}_f9V_`g|Tww}*68J=LD1ydai;(`Itq@&NwgQ=tbTUB$gL>8jlnM zBkx>>;Iq6#9OWsdw&ck(%e-TmVwRys;mA4z%z4`9Sb%zmKNNa+2T$MkIfPecA;BT+ z$2+hK6`5@#qf(#USGxpQElv}-3>q~X1$_}q*zGk@7`i9?MY|>rg7F4ehVEUtdH=3( z;mXzJo6E*$;`8rlpNqX<#Je9YIbFGVbs3ySA$6%iJ-^`O?%xu8^Kzl=w^X<5gsffEw#eZ?qsQa(dm22=?JZ4RZ7ofQ8X7%4?!*4( z0J>FV9;0Z=9wI@qSpFV&7WQ(x8NEl@?_84lULoG6<4ZvT+Hg}_6nhq!+C|t;Gx}hD z$6G>c*G^ArCW}VUBK1|@D$dUv%iuUMDs)8T*HZ=RC9?Xp8=Ci` z^(VHU1zxUAgyW~UMEIf&l<+C;wMx*&E;ykS(T`2-ramI0gWzHnl{c2Bg>F z1!ODM0+j0rM8XYo4LSw^gs~co3$OuRm$l<_auA`1&@MiH0(8ocJ z1nOMTz&6)0#|T*JP2_v7<3)LUVfVjYfA?jLP~AyY@bH73`-an*7qYqj0dIb4SuZkb-IV7N#V=4^J@g|qI&ZMPMT2di%jV$V zu#G0a2HO-dSpOA+ZN6%-E!PaT^=*S~yKb=U?-=Zk8wT5P(_jO{AP)ki{?S;efplvYc?k>Xn;U-v<`C z$n>qP-mexW#){4kc>L<9es#8>HVwoLJz8{zzYqruEehoO$s*e!h)_{S(HSVR1LleY zYGFDIbNR>9d)x!6jtUpVfXCy1wmAwTI0qmt-oXE$-T+n}YP3#3%A#{G9=s19&^ifD zCNgIt4}-*mAW>vJ0GEo0;vlRaFhj7F7f!8$d$DCL6Q1mSDf$pYrA&N8PE^^vYT@TV z&wi&Dx>Hxa6RjgEkGSg*PEXgRFCY`yjX# zepO_SKn8D!gcj(?VH;8lzX{WNIn)4O6P>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" - -from displayio import Group, Palette - -try: - from typing import Optional, List, Tuple - from fontio import FontProtocol -except ImportError: - pass - - -def wrap_text_to_pixels( - string: str, - max_width: int, - font: Optional[FontProtocol] = None, - indent0: str = "", - indent1: str = "", -) -> List[str]: - # pylint: disable=too-many-branches, too-many-locals, too-many-nested-blocks, too-many-statements - - """wrap_text_to_pixels function - A helper that will return a list of lines with word-break wrapping. - Leading and trailing whitespace in your string will be removed. If - you wish to use leading whitespace see ``indent0`` and ``indent1`` - parameters. - - :param str string: The text to be wrapped. - :param int max_width: The maximum number of pixels on a line before wrapping. - :param font: The font to use for measuring the text. - :type font: ~fontio.FontProtocol - :param str indent0: Additional character(s) to add to the first line. - :param str indent1: Additional character(s) to add to all other lines. - - :return: A list of the lines resulting from wrapping the - input text at ``max_width`` pixels size - :rtype: List[str] - - """ - if font is None: - - def measure(text): - return len(text) - - else: - if hasattr(font, "load_glyphs"): - font.load_glyphs(string) - - def measure(text): - total_len = 0 - for char in text: - this_glyph = font.get_glyph(ord(char)) - if this_glyph: - total_len += this_glyph.shift_x - return total_len - - lines = [] - partial = [indent0] - width = measure(indent0) - swidth = measure(" ") - firstword = True - for line_in_input in string.split("\n"): - newline = True - for index, word in enumerate(line_in_input.split(" ")): - wwidth = measure(word) - word_parts = [] - cur_part = "" - - if wwidth > max_width: - for char in word: - if newline: - extraspace = 0 - leadchar = "" - else: - extraspace = swidth - leadchar = " " - if ( - measure("".join(partial)) - + measure(cur_part) - + measure(char) - + measure("-") - + extraspace - > max_width - ): - if cur_part: - word_parts.append( - "".join(partial) + leadchar + cur_part + "-" - ) - - else: - word_parts.append("".join(partial)) - cur_part = char - partial = [indent1] - newline = True - else: - cur_part += char - if cur_part: - word_parts.append(cur_part) - for line in word_parts[:-1]: - lines.append(line) - partial.append(word_parts[-1]) - width = measure(word_parts[-1]) - if firstword: - firstword = False - else: - if firstword: - partial.append(word) - firstword = False - width += wwidth - elif width + swidth + wwidth < max_width: - if index > 0: - partial.append(" ") - partial.append(word) - width += wwidth + swidth - else: - lines.append("".join(partial)) - partial = [indent1, word] - width = measure(indent1) + wwidth - if newline: - newline = False - - lines.append("".join(partial)) - partial = [indent1] - width = measure(indent1) - - return lines - - -def wrap_text_to_lines(string: str, max_chars: int) -> List[str]: - """wrap_text_to_lines function - A helper that will return a list of lines with word-break wrapping - - :param str string: The text to be wrapped - :param int max_chars: The maximum number of characters on a line before wrapping - - :return: A list of lines where each line is separated based on the amount - of ``max_chars`` provided - :rtype: List[str] - """ - - def chunks(lst, n): - """Yield successive n-sized chunks from lst.""" - for i in range(0, len(lst), n): - yield lst[i : i + n] - - string = string.replace("\n", "").replace("\r", "") # Strip confusing newlines - words = string.split(" ") - the_lines = [] - the_line = "" - for w in words: - if len(w) > max_chars: - if the_line: # add what we had stored - the_lines.append(the_line) - parts = [] - for part in chunks(w, max_chars - 1): - parts.append("{}-".format(part)) - the_lines.extend(parts[:-1]) - the_line = parts[-1][:-1] - continue - - if len(the_line + " " + w) <= max_chars: - the_line += " " + w - elif not the_line and len(w) == max_chars: - the_lines.append(w) - else: - the_lines.append(the_line) - the_line = "" + w - if the_line: # Last line remaining - the_lines.append(the_line) - # Remove any blank lines - while not the_lines[0]: - del the_lines[0] - # Remove first space from first line: - if the_lines[0][0] == " ": - the_lines[0] = the_lines[0][1:] - return the_lines - - -class LabelBase(Group): - # pylint: disable=too-many-instance-attributes - - """Superclass that all other types of labels will extend. This contains - all of the properties and functions that work the same way in all labels. - - **Note:** This should be treated as an abstract base class. - - Subclasses should implement ``_set_text``, ``_set_font``, and ``_set_line_spacing`` to - have the correct behavior for that type of label. - - :param font: A font class that has ``get_bounding_box`` and ``get_glyph``. - Must include a capital M for measuring character size. - :type font: ~fontio.FontProtocol - :param str text: Text to display - :param int color: Color of all text in RGB hex - :param int background_color: Color of the background, use `None` for transparent - :param float line_spacing: Line spacing of text to display - :param bool background_tight: Set `True` only if you want background box to tightly - surround text. When set to 'True' Padding parameters will be ignored. - :param int padding_top: Additional pixels added to background bounding box at top - :param int padding_bottom: Additional pixels added to background bounding box at bottom - :param int padding_left: Additional pixels added to background bounding box at left - :param int padding_right: Additional pixels added to background bounding box at right - :param (float,float) anchor_point: Point that anchored_position moves relative to. - Tuple with decimal percentage of width and height. - (E.g. (0,0) is top left, (1.0, 0.5): is middle right.) - :param (int,int) anchored_position: Position relative to the anchor_point. Tuple - containing x,y pixel coordinates. - :param int scale: Integer value of the pixel scaling - :param bool base_alignment: when True allows to align text label to the baseline. - This is helpful when two or more labels need to be aligned to the same baseline - :param (int,str) tab_replacement: tuple with tab character replace information. When - (4, " ") will indicate a tab replacement of 4 spaces, defaults to 4 spaces by - tab character - :param str label_direction: string defining the label text orientation. See the - subclass documentation for the possible values. - :param bool verbose: print debugging information in some internal functions. Default to False - """ - - def __init__( - self, - font: FontProtocol, - x: int = 0, - y: int = 0, - text: str = "", - color: int = 0xFFFFFF, - background_color: int = None, - line_spacing: float = 1.25, - background_tight: bool = False, - padding_top: int = 0, - padding_bottom: int = 0, - padding_left: int = 0, - padding_right: int = 0, - anchor_point: Tuple[float, float] = None, - anchored_position: Tuple[int, int] = None, - scale: int = 1, - base_alignment: bool = False, - tab_replacement: Tuple[int, str] = (4, " "), - label_direction: str = "LTR", - verbose: bool = False, - ) -> None: - # pylint: disable=too-many-arguments, too-many-locals - - super().__init__(x=x, y=y, scale=1) - - self._font = font - self._text = text - self._palette = Palette(2) - self._color = 0xFFFFFF - self._background_color = None - self._line_spacing = line_spacing - self._background_tight = background_tight - self._padding_top = padding_top - self._padding_bottom = padding_bottom - self._padding_left = padding_left - self._padding_right = padding_right - self._anchor_point = anchor_point - self._anchored_position = anchored_position - self._base_alignment = base_alignment - self._label_direction = label_direction - self._tab_replacement = tab_replacement - self._tab_text = self._tab_replacement[1] * self._tab_replacement[0] - self._verbose = verbose - - self._ascent, self._descent = self._get_ascent_descent() - self._bounding_box = None - - self.color = color - self.background_color = background_color - - # local group will hold background and text - # the self group scale should always remain at 1, the self._local_group will - # be used to set the scale of the label - self._local_group = Group(scale=scale) - self.append(self._local_group) - - self._baseline = -1.0 - - if self._base_alignment: - self._y_offset = 0 - else: - self._y_offset = self._ascent // 2 - - def _get_ascent_descent(self) -> Tuple[int, int]: - """Private function to calculate ascent and descent font values""" - if hasattr(self.font, "ascent") and hasattr(self.font, "descent"): - return self.font.ascent, self.font.descent - - # check a few glyphs for maximum ascender and descender height - glyphs = "M j'" # choose glyphs with highest ascender and lowest - try: - self._font.load_glyphs(glyphs) - except AttributeError: - # Builtin font doesn't have or need load_glyphs - pass - # descender, will depend upon font used - ascender_max = descender_max = 0 - for char in glyphs: - this_glyph = self._font.get_glyph(ord(char)) - if this_glyph: - ascender_max = max(ascender_max, this_glyph.height + this_glyph.dy) - descender_max = max(descender_max, -this_glyph.dy) - return ascender_max, descender_max - - @property - def font(self) -> FontProtocol: - """Font to use for text display.""" - return self._font - - def _set_font(self, new_font: FontProtocol) -> None: - raise NotImplementedError("{} MUST override '_set_font'".format(type(self))) - - @font.setter - def font(self, new_font: FontProtocol) -> None: - self._set_font(new_font) - - @property - def color(self) -> int: - """Color of the text as an RGB hex number.""" - return self._color - - @color.setter - def color(self, new_color: int): - self._color = new_color - if new_color is not None: - self._palette[1] = new_color - self._palette.make_opaque(1) - else: - self._palette[1] = 0 - self._palette.make_transparent(1) - - @property - def background_color(self) -> int: - """Color of the background as an RGB hex number.""" - return self._background_color - - def _set_background_color(self, new_color): - raise NotImplementedError( - "{} MUST override '_set_background_color'".format(type(self)) - ) - - @background_color.setter - def background_color(self, new_color: int) -> None: - self._set_background_color(new_color) - - @property - def anchor_point(self) -> Tuple[float, float]: - """Point that anchored_position moves relative to. - Tuple with decimal percentage of width and height. - (E.g. (0,0) is top left, (1.0, 0.5): is middle right.)""" - return self._anchor_point - - @anchor_point.setter - def anchor_point(self, new_anchor_point: Tuple[float, float]) -> None: - if new_anchor_point[1] == self._baseline: - self._anchor_point = (new_anchor_point[0], -1.0) - else: - self._anchor_point = new_anchor_point - - # update the anchored_position using setter - self.anchored_position = self._anchored_position - - @property - def anchored_position(self) -> Tuple[int, int]: - """Position relative to the anchor_point. Tuple containing x,y - pixel coordinates.""" - return self._anchored_position - - @anchored_position.setter - def anchored_position(self, new_position: Tuple[int, int]) -> None: - self._anchored_position = new_position - # Calculate (x,y) position - if (self._anchor_point is not None) and (self._anchored_position is not None): - self.x = int( - new_position[0] - - (self._bounding_box[0] * self.scale) - - round(self._anchor_point[0] * (self._bounding_box[2] * self.scale)) - ) - if self._anchor_point[1] == self._baseline: - self.y = int(new_position[1] - (self._y_offset * self.scale)) - else: - self.y = int( - new_position[1] - - (self._bounding_box[1] * self.scale) - - round(self._anchor_point[1] * self._bounding_box[3] * self.scale) - ) - - @property - def scale(self) -> int: - """Set the scaling of the label, in integer values""" - return self._local_group.scale - - @scale.setter - def scale(self, new_scale: int) -> None: - self._local_group.scale = new_scale - self.anchored_position = self._anchored_position # update the anchored_position - - def _set_text(self, new_text: str, scale: int) -> None: - raise NotImplementedError("{} MUST override '_set_text'".format(type(self))) - - @property - def text(self) -> str: - """Text to be displayed.""" - return self._text - - @text.setter # Cannot set color or background color with text setter, use separate setter - def text(self, new_text: str) -> None: - self._set_text(new_text, self.scale) - - @property - def bounding_box(self) -> Tuple[int, int]: - """An (x, y, w, h) tuple that completely covers all glyphs. The - first two numbers are offset from the x, y origin of this group""" - return tuple(self._bounding_box) - - @property - def height(self) -> int: - """The height of the label determined from the bounding box.""" - return self._bounding_box[3] - - @property - def width(self) -> int: - """The width of the label determined from the bounding box.""" - return self._bounding_box[2] - - @property - def line_spacing(self) -> float: - """The amount of space between lines of text, in multiples of the font's - bounding-box height. (E.g. 1.0 is the bounding-box height)""" - return self._line_spacing - - def _set_line_spacing(self, new_line_spacing: float) -> None: - raise NotImplementedError( - "{} MUST override '_set_line_spacing'".format(type(self)) - ) - - @line_spacing.setter - def line_spacing(self, new_line_spacing: float) -> None: - self._set_line_spacing(new_line_spacing) - - @property - def label_direction(self) -> str: - """Set the text direction of the label""" - return self._label_direction - - def _set_label_direction(self, new_label_direction: str) -> None: - raise NotImplementedError( - "{} MUST override '_set_label_direction'".format(type(self)) - ) - - def _get_valid_label_directions(self) -> Tuple[str, ...]: - raise NotImplementedError( - "{} MUST override '_get_valid_label_direction'".format(type(self)) - ) - - @label_direction.setter - def label_direction(self, new_label_direction: str) -> None: - """Set the text direction of the label""" - if new_label_direction not in self._get_valid_label_directions(): - raise RuntimeError("Please provide a valid text direction") - self._set_label_direction(new_label_direction) - - def _replace_tabs(self, text: str) -> str: - return text if text.find("\t") < 0 else self._tab_text.join(text.split("\t")) diff --git a/src/lib/adafruit_display_text/bitmap_label.mpy b/src/lib/adafruit_display_text/bitmap_label.mpy deleted file mode 100644 index ef1ce62f0be706c2aa14606f9fc1816ff1c5ea49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3923 zcma)8Yj6|S6~21f2tii6Vo_|zS)|q4mccecD2X1#u!hy1{iNwU^sZ3)(r(8I_{ zAS;JVQj*TlX{R&&RXWq@ul`uc?+5lw8eYagnf^$a5c-S1os7)%ch9bji$iBxkM7-b z&pG#;bH8)$xo$P0*K^uE-GOjmG?|H}`EWFq5CfBZTF9mwgVFSOAi;})pddCRCebUV zBMnUrUW9LHMdJwXRfKFHB_P*-HG;^Jm2^BVrVw^Ak&eb=0TG!7G6_*Y=JaGD8XH5l z_INCPDj845LvazAM_~olO{#Td96Z&BjNA}RgQqxT>>U8AZ=e@B_VOj`2!?%n&RC!4xu;xFm2>Lb|jlS3*GL3J%C7 zMq>h>N(4ems1^Kd?W&4)rcsJrcu@QGO3Df+v{{ZfvUGx!FW0yA4jGO(QrC~%n?D= zHnVCT5e9mqkWLGzd^9SGh~&30P|irb1X#{j4S}TuJ|ITNV&ek9v6l~|LJENm0NNc2 z+O|YAD~Nn55(o=PWL2`jm)w&oy%mEkEQ|&+B7h4DNSj64Ni>P^Tvs1|{MadEOD6-d zR3Z-EvXhX0F`D4BTi;1U^MP0>5>E~~BkP8*8`)KjR6LV}Dl945d8PVx6c?_t)XFN6 zYCXDC?h8c&cP5@hJL;&!BF6*g1U{Y!oX-e|QWqtp14$)~wIz?r6@KBSczFes2}pbR zr$v#1qET>2!Hwd1z(l!7aiESj9y&Tzhb5Bngpf>6qTN#j{-66cbu>4G!KH+aLKx-V z&c$;VrVPgMXbc(016hP;xATfmZj%*T=xpJfJ3_^T3p?=9xmO?e4)4g=xu<1Ce3LSE(a)5~g=)Dh*Y7Qt$692cD5McAJJQ!aulHf4AoSPD;Yc zuyU8Sa#vLuSyoj}mir~l1bcQ@nMjlWMcn~6!ps)EM@O1{AGIG%1&|rIl4lTAf=+I%W1RZ(|QzlE0 ze5=o;>6Z^64T<~U&!z7_q9I?kMBQ4ChGgA3l9oW>h@NbeBt>rMKVl${yN%?qyNo>J zE+^k}?;_uKo5+BBHyN~?b(>+|2|2Xqv(6tsYoYLSV#T;LM$_C$`dp{e>C4*)7*-hj z@R$?j_u+;98R?BFjek0CBlFh%dBWz^%VVy!!1iF2^)Q|&fUXAO0ACB6;O2VKYw*j2n`@I8jO~z#BUArkFqyD| zQ*Mp>n*?q=U}L}8CRlm6P{f*P?7CuX(|Z-rvA&Fh0?E(8 zs1g^^Boq4{bHlJ?EHd#HFb11+w8i!|jCWxC0yqxi58Op1_&txfZoq`W!91fLXRpd( z6i%JOu@Z061fXRlnrXZ^zv|LMDp>FXf)b)2Q2p$+kJ$dzlag5XuBM{MT=CH>9DPL$`)*-|?-_-(4KqxTm6fUxJ*z#$b8A z=Pw$@LR+x5M_k8R?&$;e_ql?#9Z%90B}oiU<4{@yc*;>`?ZDF|t_RAhD6MgW^EVe{ zD_fv3uXgR()7!IaPj5&M=H?%9{Y$Wmlh)a8JhMdOM5jl)J_lb{QCSojLRoY@y@^jO z&(VZOcXwg&5!Z)jR)Agq5T@?U)uKncflpjt*k0XZA6`=IveNqU(k5fQzOu>eTY0!z zWXRdv=JPHS{MqBB9OEgLEr)@4{=y&f?d)s$setlu+?gn6YufX^G zN_&4*0q*4dH?!b=`np$Jyum)pO)CJdD)>EEgLwO&qo=YI@E+!70HnS}6{I_C7oJ&M zQ!u)NPb|%>5nV-?Tt#3NJ-YSTg-6^8JhQw8$gBY{9_?LxVr8~OZmbbKp!$KkijLT!h5-1h@#xuvFg=81hfUTi+Q5{J7^+X!d=gWvh{c#pMVfL zi;_pHsGh)1(inD%#xWm_DWPF~upjH6^Ip;W)quZYf4sQ3ytKHvvb^cihknyk{Pktc z@Nx0Po6VCaPx<=#uRiJeSSoW}lFUxp8HP7RsOze$qq)Q94;&LmT=wo7{KGsp;L&pL z<*fsS=^1Qr7CSwM4b5ZkE@1tO-YYsk`$G=@5I3q?AK*soKz^n$y+h9rdU`)Qo10gl zogrMe7D`0@?b-S7xVHJ(;TyN+7Jyoq8@_oP&&-34M|%s;EX)w~9_{UgxlM`vr!=%a zKMTb7($L+)a&cpNsdz6>xK@VFeAlj*zGvBCKbHaYuD{3#&&(}>wP(fI;&O5DJ|wrb zZFTW6*S(s*4x68?6i>5VKmq0~+nIa%?(ny*fB3}jFHv6s&PXK{ diff --git a/src/lib/adafruit_display_text/bitmap_label.py b/src/lib/adafruit_display_text/bitmap_label.py deleted file mode 100644 index 7caa9f4..0000000 --- a/src/lib/adafruit_display_text/bitmap_label.py +++ /dev/null @@ -1,605 +0,0 @@ -# SPDX-FileCopyrightText: 2020 Kevin Matocha -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_display_text.bitmap_label` -================================================================================ - -Text graphics handling for CircuitPython, including text boxes - - -* Author(s): Kevin Matocha - -Implementation Notes --------------------- - -**Hardware:** - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the supported boards: - https://circuitpython.org/downloads - -""" - -<<<<<<< HEAD -__version__ = "3.1.0" -======= -<<<<<<< HEAD -__version__ = "3.2.0" -======= -__version__ = "3.1.0" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" - -import displayio -from adafruit_display_text import LabelBase - -try: - import bitmaptools -except ImportError: - # We have a slower fallback for bitmaptools - pass - -try: - from typing import Optional, Tuple - from fontio import FontProtocol -except ImportError: - pass - - -# pylint: disable=too-many-instance-attributes -class Label(LabelBase): - """A label displaying a string of text that is stored in a bitmap. - Note: This ``bitmap_label.py`` library utilizes a :py:class:`~displayio.Bitmap` - to display the text. This method is memory-conserving relative to ``label.py``. - - For further reduction in memory usage, set ``save_text=False`` (text string will not - be stored and ``line_spacing`` and ``font`` are immutable with ``save_text`` - set to ``False``). - - The origin point set by ``x`` and ``y`` - properties will be the left edge of the bounding box, and in the center of a M - glyph (if its one line), or the (number of lines * linespacing + M)/2. That is, - it will try to have it be center-left as close as possible. - - :param font: A font class that has ``get_bounding_box`` and ``get_glyph``. - Must include a capital M for measuring character size. - :type font: ~fontio.FontProtocol - :param str text: Text to display - :param int|Tuple(int, int, int) color: Color of all text in HEX or RGB - :param int|Tuple(int, int, int)|None background_color: Color of the background, use `None` - for transparent - :param float line_spacing: Line spacing of text to display - :param bool background_tight: Set `True` only if you want background box to tightly - surround text. When set to 'True' Padding parameters will be ignored. - :param int padding_top: Additional pixels added to background bounding box at top - :param int padding_bottom: Additional pixels added to background bounding box at bottom - :param int padding_left: Additional pixels added to background bounding box at left - :param int padding_right: Additional pixels added to background bounding box at right - :param Tuple(float, float) anchor_point: Point that anchored_position moves relative to. - Tuple with decimal percentage of width and height. - (E.g. (0,0) is top left, (1.0, 0.5): is middle right.) - :param Tuple(int, int) anchored_position: Position relative to the anchor_point. Tuple - containing x,y pixel coordinates. - :param int scale: Integer value of the pixel scaling - :param bool save_text: Set True to save the text string as a constant in the - label structure. Set False to reduce memory use. - :param bool base_alignment: when True allows to align text label to the baseline. - This is helpful when two or more labels need to be aligned to the same baseline - :param Tuple(int, str) tab_replacement: tuple with tab character replace information. When - (4, " ") will indicate a tab replacement of 4 spaces, defaults to 4 spaces by - tab character - :param str label_direction: string defining the label text orientation. There are 5 - configurations possibles ``LTR``-Left-To-Right ``RTL``-Right-To-Left - ``UPD``-Upside Down ``UPR``-Upwards ``DWR``-Downwards. It defaults to ``LTR`` - :param bool verbose: print debugging information in some internal functions. Default to False - - """ - - # This maps label_direction to TileGrid's transpose_xy, flip_x, flip_y - _DIR_MAP = { - "UPR": (True, True, False), - "DWR": (True, False, True), - "UPD": (False, True, True), - "LTR": (False, False, False), - "RTL": (False, False, False), - } - - def __init__(self, font: FontProtocol, save_text: bool = True, **kwargs) -> None: - self._bitmap = None - self._tilegrid = None - self._prev_label_direction = None - - super().__init__(font, **kwargs) - - self._save_text = save_text - self._text = self._replace_tabs(self._text) - - # call the text updater with all the arguments. - self._reset_text( - font=font, - text=self._text, - line_spacing=self._line_spacing, - scale=self.scale, - ) - - def _reset_text( - self, - font: Optional[FontProtocol] = None, - text: Optional[str] = None, - line_spacing: Optional[float] = None, - scale: Optional[int] = None, - ) -> None: - # pylint: disable=too-many-branches, too-many-statements, too-many-locals - - # Store all the instance variables - if font is not None: - self._font = font - if line_spacing is not None: - self._line_spacing = line_spacing - - # if text is not provided as a parameter (text is None), use the previous value. - if (text is None) and self._save_text: - text = self._text - - if self._save_text: # text string will be saved - self._text = self._replace_tabs(text) - else: - self._text = None # save a None value since text string is not saved - - # Check for empty string - if (text == "") or ( - text is None - ): # If empty string, just create a zero-sized bounding box and that's it. - self._bounding_box = ( - 0, - 0, - 0, # zero width with text == "" - 0, # zero height with text == "" - ) - # Clear out any items in the self._local_group Group, in case this is an - # update to the bitmap_label - for _ in self._local_group: - self._local_group.pop(0) - - # Free the bitmap and tilegrid since they are removed - self._bitmap = None - self._tilegrid = None - - else: # The text string is not empty, so create the Bitmap and TileGrid and - # append to the self Group - - # Calculate the text bounding box - - # Calculate both "tight" and "loose" bounding box dimensions to match label for - # anchor_position calculations - ( - box_x, - tight_box_y, - x_offset, - tight_y_offset, - loose_box_y, - loose_y_offset, - ) = self._text_bounding_box( - text, - self._font, - ) # calculate the box size for a tight and loose backgrounds - - if self._background_tight: - box_y = tight_box_y - y_offset = tight_y_offset - self._padding_left = 0 - self._padding_right = 0 - self._padding_top = 0 - self._padding_bottom = 0 - - else: # calculate the box size for a loose background - box_y = loose_box_y - y_offset = loose_y_offset - - # Calculate the background size including padding - tight_box_x = box_x - box_x = box_x + self._padding_left + self._padding_right - box_y = box_y + self._padding_top + self._padding_bottom - - # Create the Bitmap unless it can be reused - new_bitmap = None - if ( - self._bitmap is None - or self._bitmap.width != box_x - or self._bitmap.height != box_y - ): - new_bitmap = displayio.Bitmap(box_x, box_y, len(self._palette)) - self._bitmap = new_bitmap - else: - self._bitmap.fill(0) - - # Place the text into the Bitmap - self._place_text( - self._bitmap, - text if self._label_direction != "RTL" else "".join(reversed(text)), - self._font, - self._padding_left - x_offset, - self._padding_top + y_offset, - ) - - if self._base_alignment: - label_position_yoffset = 0 - else: - label_position_yoffset = self._ascent // 2 - - # Create the TileGrid if not created bitmap unchanged - if self._tilegrid is None or new_bitmap: - self._tilegrid = displayio.TileGrid( - self._bitmap, - pixel_shader=self._palette, - width=1, - height=1, - tile_width=box_x, - tile_height=box_y, - default_tile=0, - x=-self._padding_left + x_offset, - y=label_position_yoffset - y_offset - self._padding_top, - ) - # Clear out any items in the local_group Group, in case this is an update to - # the bitmap_label - for _ in self._local_group: - self._local_group.pop(0) - self._local_group.append( - self._tilegrid - ) # add the bitmap's tilegrid to the group - - # Set TileGrid properties based on label_direction - if self._label_direction != self._prev_label_direction: - tg1 = self._tilegrid - tg1.transpose_xy, tg1.flip_x, tg1.flip_y = self._DIR_MAP[ - self._label_direction - ] - - # Update bounding_box values. Note: To be consistent with label.py, - # this is the bounding box for the text only, not including the background. - if self._label_direction in ("UPR", "DWR"): - if self._label_direction == "UPR": - top = self._padding_right - left = self._padding_top - if self._label_direction == "DWR": - top = self._padding_left - left = self._padding_bottom - self._bounding_box = ( - self._tilegrid.x + left, - self._tilegrid.y + top, - tight_box_y, - tight_box_x, - ) - else: - self._bounding_box = ( - self._tilegrid.x + self._padding_left, - self._tilegrid.y + self._padding_top, - tight_box_x, - tight_box_y, - ) - - if ( - scale is not None - ): # Scale will be defined in local_group (Note: self should have scale=1) - self.scale = scale # call the setter - - # set the anchored_position with setter after bitmap is created, sets the - # x,y positions of the label - self.anchored_position = self._anchored_position - - @staticmethod - def _line_spacing_ypixels(font: FontProtocol, line_spacing: float) -> int: - # Note: Scaling is provided at the Group level - return_value = int(line_spacing * font.get_bounding_box()[1]) - return return_value - - def _text_bounding_box( - self, text: str, font: FontProtocol - ) -> Tuple[int, int, int, int, int, int]: - # pylint: disable=too-many-locals,too-many-branches - - bbox = font.get_bounding_box() - if len(bbox) == 4: - ascender_max, descender_max = bbox[1], -bbox[3] - else: - ascender_max, descender_max = self._ascent, self._descent - - lines = 1 - - # starting x and y position (left margin) - xposition = x_start = yposition = y_start = 0 - - left = None - right = x_start - top = bottom = y_start - - y_offset_tight = self._ascent // 2 - - newlines = 0 - line_spacing = self._line_spacing - - for char in text: - if char == "\n": # newline - newlines += 1 - - else: - my_glyph = font.get_glyph(ord(char)) - - if my_glyph is None: # Error checking: no glyph found - print("Glyph not found: {}".format(repr(char))) - else: - if newlines: - xposition = x_start # reset to left column - yposition += ( - self._line_spacing_ypixels(font, line_spacing) * newlines - ) # Add the newline(s) - lines += newlines - newlines = 0 - if xposition == x_start: - if left is None: - left = 0 - else: - left = min(left, my_glyph.dx) - xright = xposition + my_glyph.width + my_glyph.dx - xposition += my_glyph.shift_x - - right = max(right, xposition, xright) - - if yposition == y_start: # first line, find the Ascender height - top = min(top, -my_glyph.height - my_glyph.dy + y_offset_tight) - bottom = max(bottom, yposition - my_glyph.dy + y_offset_tight) - - if left is None: - left = 0 - - final_box_width = right - left - - final_box_height_tight = bottom - top - final_y_offset_tight = -top + y_offset_tight - - final_box_height_loose = (lines - 1) * self._line_spacing_ypixels( - font, line_spacing - ) + (ascender_max + descender_max) - final_y_offset_loose = ascender_max - - # return (final_box_width, final_box_height, left, final_y_offset) - - return ( - final_box_width, - final_box_height_tight, - left, - final_y_offset_tight, - final_box_height_loose, - final_y_offset_loose, - ) - - # pylint: disable = too-many-branches - def _place_text( - self, - bitmap: displayio.Bitmap, - text: str, - font: FontProtocol, - xposition: int, - yposition: int, - skip_index: int = 0, # set to None to write all pixels, other wise skip this palette index - # when copying glyph bitmaps (this is important for slanted text - # where rectangular glyph boxes overlap) - ) -> Tuple[int, int, int, int]: - # pylint: disable=too-many-arguments, too-many-locals - - # placeText - Writes text into a bitmap at the specified location. - # - # Note: scale is pushed up to Group level - - x_start = xposition # starting x position (left margin) - y_start = yposition - - left = None - right = x_start - top = bottom = y_start - line_spacing = self._line_spacing - - for char in text: - if char == "\n": # newline - xposition = x_start # reset to left column - yposition = yposition + self._line_spacing_ypixels( - font, line_spacing - ) # Add a newline - - else: - my_glyph = font.get_glyph(ord(char)) - - if my_glyph is None: # Error checking: no glyph found - print("Glyph not found: {}".format(repr(char))) - else: - if xposition == x_start: - if left is None: - left = 0 - else: - left = min(left, my_glyph.dx) - - right = max( - right, - xposition + my_glyph.shift_x, - xposition + my_glyph.width + my_glyph.dx, - ) - if yposition == y_start: # first line, find the Ascender height - top = min(top, -my_glyph.height - my_glyph.dy) - bottom = max(bottom, yposition - my_glyph.dy) - - glyph_offset_x = ( - my_glyph.tile_index * my_glyph.width - ) # for type BuiltinFont, this creates the x-offset in the glyph bitmap. - # for BDF loaded fonts, this should equal 0 - - y_blit_target = yposition - my_glyph.height - my_glyph.dy - - # Clip glyph y-direction if outside the font ascent/descent metrics. - # Note: bitmap.blit will automatically clip the bottom of the glyph. - y_clip = 0 - if y_blit_target < 0: - y_clip = -y_blit_target # clip this amount from top of bitmap - y_blit_target = 0 # draw the clipped bitmap at y=0 - if self._verbose: - print( - 'Warning: Glyph clipped, exceeds Ascent property: "{}"'.format( - char - ) - ) - - if (y_blit_target + my_glyph.height) > bitmap.height: - if self._verbose: - print( - 'Warning: Glyph clipped, exceeds descent property: "{}"'.format( - char - ) - ) - - self._blit( - bitmap, - max(xposition + my_glyph.dx, 0), - y_blit_target, - my_glyph.bitmap, - x_1=glyph_offset_x, - y_1=y_clip, - x_2=glyph_offset_x + my_glyph.width, - y_2=my_glyph.height, - skip_index=skip_index, # do not copy over any 0 background pixels - ) - - xposition = xposition + my_glyph.shift_x - - # bounding_box - return left, top, right - left, bottom - top - - def _blit( - self, - bitmap: displayio.Bitmap, # target bitmap - x: int, # target x upper left corner - y: int, # target y upper left corner - source_bitmap: displayio.Bitmap, # source bitmap - x_1: int = 0, # source x start - y_1: int = 0, # source y start - x_2: int = None, # source x end - y_2: int = None, # source y end - skip_index: int = None, # palette index that will not be copied - # (for example: the background color of a glyph) - ) -> None: - # pylint: disable=no-self-use, too-many-arguments - - if hasattr(bitmap, "blit"): # if bitmap has a built-in blit function, call it - # this function should perform its own input checks - bitmap.blit( - x, - y, - source_bitmap, - x1=x_1, - y1=y_1, - x2=x_2, - y2=y_2, - skip_index=skip_index, - ) - elif hasattr(bitmaptools, "blit"): - bitmaptools.blit( - bitmap, - source_bitmap, - x, - y, - x1=x_1, - y1=y_1, - x2=x_2, - y2=y_2, - skip_source_index=skip_index, - ) - - else: # perform pixel by pixel copy of the bitmap - # Perform input checks - - if x_2 is None: - x_2 = source_bitmap.width - if y_2 is None: - y_2 = source_bitmap.height - - # Rearrange so that x_1 < x_2 and y1 < y2 - if x_1 > x_2: - x_1, x_2 = x_2, x_1 - if y_1 > y_2: - y_1, y_2 = y_2, y_1 - - # Ensure that x2 and y2 are within source bitmap size - x_2 = min(x_2, source_bitmap.width) - y_2 = min(y_2, source_bitmap.height) - - for y_count in range(y_2 - y_1): - for x_count in range(x_2 - x_1): - x_placement = x + x_count - y_placement = y + y_count - - if (bitmap.width > x_placement >= 0) and ( - bitmap.height > y_placement >= 0 - ): # ensure placement is within target bitmap - # get the palette index from the source bitmap - this_pixel_color = source_bitmap[ - y_1 - + ( - y_count * source_bitmap.width - ) # Direct index into a bitmap array is speedier than [x,y] tuple - + x_1 - + x_count - ] - - if (skip_index is None) or (this_pixel_color != skip_index): - bitmap[ # Direct index into a bitmap array is speedier than [x,y] tuple - y_placement * bitmap.width + x_placement - ] = this_pixel_color - elif y_placement > bitmap.height: - break - - def _set_line_spacing(self, new_line_spacing: float) -> None: - if self._save_text: - self._reset_text(line_spacing=new_line_spacing, scale=self.scale) - else: - raise RuntimeError("line_spacing is immutable when save_text is False") - - def _set_font(self, new_font: FontProtocol) -> None: - self._font = new_font - if self._save_text: - self._reset_text(font=new_font, scale=self.scale) - else: - raise RuntimeError("font is immutable when save_text is False") - - def _set_text(self, new_text: str, scale: int) -> None: - self._reset_text(text=self._replace_tabs(new_text), scale=self.scale) - - def _set_background_color(self, new_color: Optional[int]): - self._background_color = new_color - if new_color is not None: - self._palette[0] = new_color - self._palette.make_opaque(0) - else: - self._palette[0] = 0 - self._palette.make_transparent(0) - - def _set_label_direction(self, new_label_direction: str) -> None: - # Only make changes if new direction is different - # to prevent errors in the _reset_text() direction checks - if self._label_direction != new_label_direction: - self._prev_label_direction = self._label_direction - self._label_direction = new_label_direction - self._reset_text(text=str(self._text)) # Force a recalculation - - def _get_valid_label_directions(self) -> Tuple[str, ...]: - return "LTR", "RTL", "UPD", "UPR", "DWR" - - @property - def bitmap(self) -> displayio.Bitmap: - """ - The Bitmap object that the text and background are drawn into. - - :rtype: displayio.Bitmap - """ - return self._bitmap diff --git a/src/lib/adafruit_display_text/label.mpy b/src/lib/adafruit_display_text/label.mpy deleted file mode 100644 index 2f829b645e9ae9e7699631342d42ff85d16d817e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3597 zcmZ`)TTmO<89tH_7e&R(uDn(;byVFfgt=Ij<;2D@S=K^eY%T^PoVd$MS|l|B$Kd$9kNYk&yd;0wZc5CC8J~VgeaPW>N_W8Rcv$ zJ{?77e{xzLOef`JIGI4kNmv1(fdC?#QwWLSkD}@1%ydLZRVa7`F%pp?hmT|+6~&|) zg|r0E2}^=3hBC-No`sCU$MJ|9gC)jCmZ&5PA&dzOK$u1KLQINBV=|%+tXQVRj{qet zPG?eLS^_l|urr{EJ%!E*;j|>m(qR@DsqrYV_3*h&Ow?hO&JR*c%nkY3MtP|3t3|12HX-NQPVdBJtQ#qMJXAV}Oq%0?=Q1!?NhpPFpVK@pw zkOVOik4{fv4%MTB!^oV9&q@g)6B8p+8fj;dHjCT>e(E9bLwJ&DWEK+1u$T~VNKzVrxf*nWPSUo(_kE;7eh7pZ}5=~@N zF;o@FB4Z^W^_f_FQWj>Bp&Y`h$Sg93%1LRCNR#4B0wMz8M5MZzSPte%QbqYT+8Cq6zqLf?>qXiCUQ4vulwcgJKomATyB z9*xVfnNV9eIn};j-tF)2H<0dlIt-0uFe}I40eq#Mj6jjJfgUnw^*Wq-dL2$RgSJM~ z0snXxM_sSO31HA4UX^d|YvJ7ejs0AsX2PPF?2&i+t)9^^rDca~h#gia9aTl?-MZ7& zO*&TBRK->`=~?}_Le;o6r)!&P*qZTxMWN~_gIjz0t)@D*uF1d};M@r3KWj3w#_>MG zSi|kV@ZIBmntLN2MAbLccy(<3P+0RYQQKPoX|v*f#5}zF@ZE=>N4g%?MJ$ns1vQMY zqf|Y6N3|dgd%~k(PkXd%t4GIP@>H>n9zEOVsb>2F0`1GrlEOLzDbWdw z)8TYlD$}HlK2w=r z@M9q6N7rHeWb`X#1LoZ5CimBz%A8S|x9@IktBl2_`HoMM8!Ge5lFC?1U%{}uv9emC zg+gvmWlTS+jO|CS1`h4;bClM(e<+|yIbZSjE1Knl@5XqKVZxg;p$N3j`f4yYY5 zbx7d=(L$Yl#rwhTbM8*XY9BCpt~^z?I37zX2M--VPVS8yI!@MRA84si69cFP3*W|3a|@Gp$=(%6WA!Y5kX!TV*J! zWsN%bh4+!>>nGvQYKQ%;K~eeRM3!MtfxoFTZKigd0E4Qzfg8Yw9e7y)3DQF~pH~^ivq4?lmv<+Qt;ypwY}+pN}B8|QCscX)2O zybcH79I%b!ska%Ajy|t#)Hwk1b%(p%>va!-=x{sS9o`mae}@$oj;St}-R`pUErDhp za{p|=1*6mD?`gHdA7{NCYjd@_THx;{?}F3-hc)ngZk^+l^+iYk7w=b8vyTf9cY=h2 z{?04DPZ`@7+uR!3=I-L1cgfB+-r3*+%Gl=E_J*=m5N>={{roX?;T1o;Knp5!s$3DT zpD9l{y9#xFnGQe3h5TZzo8xGkwnMw~67X!g@A%Ji^u)@-8gm6o$yf4fw-ywDp(D#{ zOy};K?M=ASxwpT@%9u=so>-kstY0*MZS4@e$Zr z0UfZj1#bk!&WZ;&fB>Mqw+C1EK)AZ+)d3aks7~yt&JuPbIO^aMHv7i8J6wtMFX#aO zRLF~#_YH57w_U?;!yW(%;9N`rY!>GMg+vjsH_A^hKNE9!^Yl&#GB$mUy!Hw-Iv@D9 z3kX9o{-6L8nE$5G`ZcBt_ZBnt{u;wk-Ee{8;go9mf;#u5_qUp_E8YnVKQUj2A2dPI zw)WROOSlcRc{P9H15ZF$y2JJ2S`R#dma>$4P5K_>xF2|5-GfF#Vmk&V@}NOTKjZM+ zS<025U`QzZcz=(qaEC14!e@|;1gP|MG530%`&YKI*@H8Y!(SItP(3)iI0b)Na3=L& z*K*1xEMdJ)n4e z3wRz?g|^D5%z6CD?Nv=+^gQ|C=FMDvV=y%LdwYk<^gdNhm*@{w_=fv55~P>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" - - -from displayio import Bitmap, Palette, TileGrid -from adafruit_display_text import LabelBase - -try: - from typing import Optional, Tuple - from fontio import FontProtocol -except ImportError: - pass - - -class Label(LabelBase): - # pylint: disable=too-many-instance-attributes - - """A label displaying a string of text. The origin point set by ``x`` and ``y`` - properties will be the left edge of the bounding box, and in the center of a M - glyph (if its one line), or the (number of lines * linespacing + M)/2. That is, - it will try to have it be center-left as close as possible. - - :param font: A font class that has ``get_bounding_box`` and ``get_glyph``. - Must include a capital M for measuring character size. - :type font: ~fontio.FontProtocol - :param str text: Text to display - :param int|Tuple(int, int, int) color: Color of all text in HEX or RGB - :param int|Tuple(int, int, int)|None background_color: Color of the background, use `None` - for transparent - :param float line_spacing: Line spacing of text to display - :param bool background_tight: Set `True` only if you want background box to tightly - surround text. When set to 'True' Padding parameters will be ignored. - :param int padding_top: Additional pixels added to background bounding box at top. - This parameter could be negative indicating additional pixels subtracted from the - background bounding box. - :param int padding_bottom: Additional pixels added to background bounding box at bottom. - This parameter could be negative indicating additional pixels subtracted from the - background bounding box. - :param int padding_left: Additional pixels added to background bounding box at left. - This parameter could be negative indicating additional pixels subtracted from the - background bounding box. - :param int padding_right: Additional pixels added to background bounding box at right. - This parameter could be negative indicating additional pixels subtracted from the - background bounding box. - :param Tuple(float, float) anchor_point: Point that anchored_position moves relative to. - Tuple with decimal percentage of width and height. - (E.g. (0,0) is top left, (1.0, 0.5): is middle right.) - :param Tuple(int, int) anchored_position: Position relative to the anchor_point. Tuple - containing x,y pixel coordinates. - :param int scale: Integer value of the pixel scaling - :param bool base_alignment: when True allows to align text label to the baseline. - This is helpful when two or more labels need to be aligned to the same baseline - :param Tuple(int, str) tab_replacement: tuple with tab character replace information. When - (4, " ") will indicate a tab replacement of 4 spaces, defaults to 4 spaces by - tab character - :param str label_direction: string defining the label text orientation. There are 5 - configurations possibles ``LTR``-Left-To-Right ``RTL``-Right-To-Left - ``TTB``-Top-To-Bottom ``UPR``-Upwards ``DWR``-Downwards. It defaults to ``LTR``""" - - def __init__(self, font: FontProtocol, **kwargs) -> None: - self._background_palette = Palette(1) - self._added_background_tilegrid = False - - super().__init__(font, **kwargs) - - text = self._replace_tabs(self._text) - - self._width = len(text) - self._height = self._font.get_bounding_box()[1] - - # Create the two-color text palette - self._palette[0] = 0 - self._palette.make_transparent(0) - - if text is not None: - self._reset_text(str(text)) - - # pylint: disable=too-many-branches - def _create_background_box(self, lines: int, y_offset: int) -> TileGrid: - """Private Class function to create a background_box - :param lines: int number of lines - :param y_offset: int y pixel bottom coordinate for the background_box""" - - left = self._bounding_box[0] - if self._background_tight: # draw a tight bounding box - box_width = self._bounding_box[2] - box_height = self._bounding_box[3] - x_box_offset = 0 - y_box_offset = self._bounding_box[1] - - else: # draw a "loose" bounding box to include any ascenders/descenders. - ascent, descent = self._ascent, self._descent - - if self._label_direction in ("DWR", "UPR"): - box_height = ( - self._bounding_box[3] + self._padding_right + self._padding_left - ) - x_box_offset = -self._padding_left - box_width = ( - (ascent + descent) - + int((lines - 1) * self._width * self._line_spacing) - + self._padding_top - + self._padding_bottom - ) - elif self._label_direction == "TTB": - box_height = ( - self._bounding_box[3] + self._padding_top + self._padding_bottom - ) - x_box_offset = -self._padding_left - box_width = ( - (ascent + descent) - + int((lines - 1) * self._height * self._line_spacing) - + self._padding_right - + self._padding_left - ) - else: - box_width = ( - self._bounding_box[2] + self._padding_left + self._padding_right - ) - x_box_offset = -self._padding_left - box_height = ( - (ascent + descent) - + int((lines - 1) * self._height * self._line_spacing) - + self._padding_top - + self._padding_bottom - ) - - if self._label_direction == "DWR": - padding_to_use = self._padding_bottom - elif self._label_direction == "TTB": - padding_to_use = self._padding_top - y_offset = 0 - ascent = 0 - else: - padding_to_use = self._padding_top - - if self._base_alignment: - y_box_offset = -ascent - padding_to_use - else: - y_box_offset = -ascent + y_offset - padding_to_use - - box_width = max(0, box_width) # remove any negative values - box_height = max(0, box_height) # remove any negative values - - if self._label_direction == "UPR": - movx = y_box_offset - movy = -box_height - x_box_offset - elif self._label_direction == "DWR": - movx = y_box_offset - movy = x_box_offset - elif self._label_direction == "TTB": - movx = x_box_offset - movy = y_box_offset - else: - movx = left + x_box_offset - movy = y_box_offset - - background_bitmap = Bitmap(box_width, box_height, 1) - tile_grid = TileGrid( - background_bitmap, - pixel_shader=self._background_palette, - x=movx, - y=movy, - ) - - return tile_grid - - # pylint: enable=too-many-branches - def _set_background_color(self, new_color: Optional[int]) -> None: - """Private class function that allows updating the font box background color - - :param int new_color: Color as an RGB hex number, setting to None makes it transparent - """ - - if new_color is None: - self._background_palette.make_transparent(0) - if self._added_background_tilegrid: - self._local_group.pop(0) - self._added_background_tilegrid = False - else: - self._background_palette.make_opaque(0) - self._background_palette[0] = new_color - self._background_color = new_color - - lines = self._text.rstrip("\n").count("\n") + 1 - y_offset = self._ascent // 2 - - if self._bounding_box is None: - # Still in initialization - return - - if not self._added_background_tilegrid: # no bitmap is in the self Group - # add bitmap if text is present and bitmap sizes > 0 pixels - if ( - (len(self._text) > 0) - and ( - self._bounding_box[2] + self._padding_left + self._padding_right > 0 - ) - and ( - self._bounding_box[3] + self._padding_top + self._padding_bottom > 0 - ) - ): - self._local_group.insert( - 0, self._create_background_box(lines, y_offset) - ) - self._added_background_tilegrid = True - - else: # a bitmap is present in the self Group - # update bitmap if text is present and bitmap sizes > 0 pixels - if ( - (len(self._text) > 0) - and ( - self._bounding_box[2] + self._padding_left + self._padding_right > 0 - ) - and ( - self._bounding_box[3] + self._padding_top + self._padding_bottom > 0 - ) - ): - self._local_group[0] = self._create_background_box( - lines, self._y_offset - ) - else: # delete the existing bitmap - self._local_group.pop(0) - self._added_background_tilegrid = False - - def _update_text(self, new_text: str) -> None: - # pylint: disable=too-many-branches,too-many-statements - - x = 0 - y = 0 - if self._added_background_tilegrid: - i = 1 - else: - i = 0 - tilegrid_count = i - if self._base_alignment: - self._y_offset = 0 - else: - self._y_offset = self._ascent // 2 - - if self._label_direction == "RTL": - left = top = bottom = 0 - right = None - elif self._label_direction == "LTR": - right = top = bottom = 0 - left = None - else: - top = right = left = 0 - bottom = 0 - - for character in new_text: - if character == "\n": - y += int(self._height * self._line_spacing) - x = 0 - continue - glyph = self._font.get_glyph(ord(character)) - if not glyph: - continue - - position_x, position_y = 0, 0 - - if self._label_direction in ("LTR", "RTL"): - bottom = max(bottom, y - glyph.dy + self._y_offset) - if y == 0: # first line, find the Ascender height - top = min(top, -glyph.height - glyph.dy + self._y_offset) - position_y = y - glyph.height - glyph.dy + self._y_offset - - if self._label_direction == "LTR": - right = max(right, x + glyph.shift_x, x + glyph.width + glyph.dx) - if x == 0: - if left is None: - left = 0 - else: - left = min(left, glyph.dx) - position_x = x + glyph.dx - else: - left = max( - left, abs(x) + glyph.shift_x, abs(x) + glyph.width + glyph.dx - ) - if x == 0: - if right is None: - right = 0 - else: - right = max(right, glyph.dx) - position_x = x - glyph.width - - elif self._label_direction == "TTB": - if x == 0: - if left is None: - left = 0 - else: - left = min(left, glyph.dx) - if y == 0: - top = min(top, -glyph.dy) - - bottom = max(bottom, y + glyph.height, y + glyph.height + glyph.dy) - right = max( - right, x + glyph.width + glyph.dx, x + glyph.shift_x + glyph.dx - ) - position_y = y + glyph.dy - position_x = x - glyph.width // 2 + self._y_offset - - elif self._label_direction == "UPR": - if x == 0: - if bottom is None: - bottom = -glyph.dx - - if y == 0: # first line, find the Ascender height - bottom = min(bottom, -glyph.dy) - left = min(left, x - glyph.height + self._y_offset) - top = min(top, y - glyph.width - glyph.dx, y - glyph.shift_x) - right = max(right, x + glyph.height, x + glyph.height - glyph.dy) - position_y = y - glyph.width - glyph.dx - position_x = x - glyph.height - glyph.dy + self._y_offset - - elif self._label_direction == "DWR": - if y == 0: - if top is None: - top = -glyph.dx - top = min(top, -glyph.dx) - if x == 0: - left = min(left, -glyph.dy) - left = min(left, x, x - glyph.dy - self._y_offset) - bottom = max(bottom, y + glyph.width + glyph.dx, y + glyph.shift_x) - right = max(right, x + glyph.height) - position_y = y + glyph.dx - position_x = x + glyph.dy - self._y_offset - - if glyph.width > 0 and glyph.height > 0: - face = TileGrid( - glyph.bitmap, - pixel_shader=self._palette, - default_tile=glyph.tile_index, - tile_width=glyph.width, - tile_height=glyph.height, - x=position_x, - y=position_y, - ) - - if self._label_direction == "UPR": - face.transpose_xy = True - face.flip_x = True - if self._label_direction == "DWR": - face.transpose_xy = True - face.flip_y = True - - if tilegrid_count < len(self._local_group): - self._local_group[tilegrid_count] = face - else: - self._local_group.append(face) - tilegrid_count += 1 - - if self._label_direction == "RTL": - x = x - glyph.shift_x - if self._label_direction == "TTB": - if glyph.height < 2: - y = y + glyph.shift_x - else: - y = y + glyph.height + 1 - if self._label_direction == "UPR": - y = y - glyph.shift_x - if self._label_direction == "DWR": - y = y + glyph.shift_x - if self._label_direction == "LTR": - x = x + glyph.shift_x - - i += 1 - - if self._label_direction == "LTR" and left is None: - left = 0 - if self._label_direction == "RTL" and right is None: - right = 0 - if self._label_direction == "TTB" and top is None: - top = 0 - - while len(self._local_group) > tilegrid_count: # i: - self._local_group.pop() - - if self._label_direction == "RTL": - # pylint: disable=invalid-unary-operand-type - # type-checkers think left can be None - self._bounding_box = (-left, top, left - right, bottom - top) - if self._label_direction == "TTB": - self._bounding_box = (left, top, right - left, bottom - top) - if self._label_direction == "UPR": - self._bounding_box = (left, top, right, bottom - top) - if self._label_direction == "DWR": - self._bounding_box = (left, top, right, bottom - top) - if self._label_direction == "LTR": - self._bounding_box = (left, top, right - left, bottom - top) - - self._text = new_text - - if self._background_color is not None: - self._set_background_color(self._background_color) - - def _reset_text(self, new_text: str) -> None: - current_anchored_position = self.anchored_position - self._update_text(str(self._replace_tabs(new_text))) - self.anchored_position = current_anchored_position - - def _set_font(self, new_font: FontProtocol) -> None: - old_text = self._text - current_anchored_position = self.anchored_position - self._text = "" - self._font = new_font - self._height = self._font.get_bounding_box()[1] - self._update_text(str(old_text)) - self.anchored_position = current_anchored_position - - def _set_line_spacing(self, new_line_spacing: float) -> None: - self._line_spacing = new_line_spacing - self.text = self._text # redraw the box - - def _set_text(self, new_text: str, scale: int) -> None: - self._reset_text(new_text) - - def _set_label_direction(self, new_label_direction: str) -> None: - self._label_direction = new_label_direction - self._update_text(str(self._text)) - - def _get_valid_label_directions(self) -> Tuple[str, ...]: - return "LTR", "RTL", "UPR", "DWR", "TTB" diff --git a/src/lib/adafruit_display_text/outlined_label.py b/src/lib/adafruit_display_text/outlined_label.py deleted file mode 100644 index 6540fba..0000000 --- a/src/lib/adafruit_display_text/outlined_label.py +++ /dev/null @@ -1,196 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Tim C -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_display_text.outlined_label` -==================================================== - -Subclass of BitmapLabel that adds outline color and stroke size -functionalities. - -* Author(s): Tim Cocks - -Implementation Notes --------------------- - -**Hardware:** - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the supported boards: - https://circuitpython.org/downloads - -""" -<<<<<<< HEAD -__version__ = "3.1.0" -======= -<<<<<<< HEAD -__version__ = "3.2.0" -======= -__version__ = "3.1.0" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" - -import bitmaptools -from displayio import Palette, Bitmap -from adafruit_display_text import bitmap_label - -try: - from typing import Optional, Tuple, Union - from fontio import FontProtocol -except ImportError: - pass - - -class OutlinedLabel(bitmap_label.Label): - """ - OutlinedLabel - A BitmapLabel subclass that includes arguments and properties for specifying - outline_size and outline_color to get drawn as a stroke around the text. - - :param Union[Tuple, int] outline_color: The color of the outline stroke as RGB tuple, or hex. - :param int outline_size: The size in pixels of the outline stroke. - - """ - - # pylint: disable=too-many-arguments - def __init__( - self, - font, - outline_color: Union[int, Tuple] = 0x999999, - outline_size: int = 1, - padding_top: Optional[int] = None, - padding_bottom: Optional[int] = None, - padding_left: Optional[int] = None, - padding_right: Optional[int] = None, - **kwargs - ): - if padding_top is None: - padding_top = outline_size + 0 - if padding_bottom is None: - padding_bottom = outline_size + 2 - if padding_left is None: - padding_left = outline_size + 0 - if padding_right is None: - padding_right = outline_size + 0 - - super().__init__( - font, - padding_top=padding_top, - padding_bottom=padding_bottom, - padding_left=padding_left, - padding_right=padding_right, - **kwargs - ) - - _background_color = self._palette[0] - _foreground_color = self._palette[1] - _background_is_transparent = self._palette.is_transparent(0) - self._palette = Palette(3) - self._palette[0] = _background_color - self._palette[1] = _foreground_color - self._palette[2] = outline_color - if _background_is_transparent: - self._palette.make_transparent(0) - - self._outline_size = outline_size - self._stamp_source = Bitmap((outline_size * 2) + 1, (outline_size * 2) + 1, 3) - self._stamp_source.fill(2) - - self._bitmap = None - - self._reset_text( - font=font, - text=self._text, - line_spacing=self._line_spacing, - scale=self.scale, - ) - - def _add_outline(self): - """ - Blit the outline into the labels Bitmap. We will stamp self._stamp_source for each - pixel of the foreground color but skip the foreground color when we blit. - :return: None - """ - if hasattr(self, "_stamp_source"): - for y in range(self.bitmap.height): - for x in range(self.bitmap.width): - if self.bitmap[x, y] == 1: - try: - bitmaptools.blit( - self.bitmap, - self._stamp_source, - x - self._outline_size, - y - self._outline_size, - skip_dest_index=1, - ) - except ValueError as value_error: - raise ValueError( - "Padding must be big enough to fit outline_size " - "all the way around the text. " - "Try using either larger padding sizes, or smaller outline_size." - ) from value_error - - def _place_text( - self, - bitmap: Bitmap, - text: str, - font: FontProtocol, - xposition: int, - yposition: int, - skip_index: int = 0, # set to None to write all pixels, other wise skip this palette index - # when copying glyph bitmaps (this is important for slanted text - # where rectangular glyph boxes overlap) - ) -> Tuple[int, int, int, int]: - """ - Copy the glpyphs that represent the value of the string into the labels Bitmap. - :param bitmap: The bitmap to place text into - :param text: The text to render - :param font: The font to render the text in - :param xposition: x location of the starting point within the bitmap - :param yposition: y location of the starting point within the bitmap - :param skip_index: Color index to skip during rendering instead of covering up - :return Tuple bounding_box: tuple with x, y, width, height values of the bitmap - """ - parent_result = super()._place_text( - bitmap, text, font, xposition, yposition, skip_index=skip_index - ) - - self._add_outline() - - return parent_result - - @property - def outline_color(self): - """Color of the outline to draw around the text.""" - return self._palette[2] - - @outline_color.setter - def outline_color(self, new_outline_color): - self._palette[2] = new_outline_color - - @property - def outline_size(self): - """Stroke size of the outline to draw around the text.""" - return self._outline_size - - @outline_size.setter - def outline_size(self, new_outline_size): - self._outline_size = new_outline_size - - self._padding_top = new_outline_size + 0 - self._padding_bottom = new_outline_size + 2 - self._padding_left = new_outline_size + 0 - self._padding_right = new_outline_size + 0 - - self._stamp_source = Bitmap( - (new_outline_size * 2) + 1, (new_outline_size * 2) + 1, 3 - ) - self._stamp_source.fill(2) - self._reset_text( - font=self._font, - text=self._text, - line_spacing=self._line_spacing, - scale=self.scale, - ) diff --git a/src/lib/adafruit_display_text/scrolling_label.mpy b/src/lib/adafruit_display_text/scrolling_label.mpy deleted file mode 100644 index 6a62a3c8949058cd016fc34528ad2c542a3ab455..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1126 zcmZuu-%s0C6uwSEaNI0h+lg_4x1_`kCP2bcnn`=u7_m^*F^z~)nn{x}7t>fuY|puD zWC+!XRt-gcnzX0=6LCws*G;4S7bc-fecDss`qanW{6HI0U+($NcYb{5oLdQlbTP20 z@9PKdp^3G9v*k4OwuX%(Tx=oNZZu5mf!5IXj7Gs}gDK`_VYyHSG&Y+C5cf>n)SXcn z&uK(=l)|)--Y`Jws$l5&1klGPgLiN(AZXh6M1-uWX#i@PYdAJR zUlo^bLC3Wn!^LfIt;^GceL?pYb6~s30G%DqGJY5@0S9o{ZK6`g*lB%QEIu%C{cx{< z>}K)uz{M{v|EprUi2U1T8`p`hI{v-8WS#=419Wh?aPtvfOi{ZTFCnYBjoHO*1Oq}Q z3xw-lbtWhTJs8SUGZRus2-R+C(cJ{SesokNzy;}m1 zVMF}2m7u^>5%qI4v%s9rc`>y581{0fbZ_!B(xXo!fXxarWP}?%7|l{%KD5=p4k#hE znWqS%C}En-l>#f2Ah8nvK`A&yxl)MOB%376#1Q35VPey4nlO_C)JOCq_~xfSC^4B7EqJ>{DOl&K_<<{*`(g#^Z^z zGIf6Di-I4YE`wSXJ-+{jDo@WT3*MI=Jy%>>me)ScIty}Eu`*_1Ugd&uu?+rtQrYl% z*5{|*KD|(X?Qn}epIo7Qp1V-*jnq?w@%fo@phhIwD)M=Wgx`)cFVwAJoNzsmL(Ohv%A{o1=PJi%O#b9*HaW( o_WyS|zfAo}ss*nvO8xQmh+tarei>9IQ9Z?^N#SoKDOpAT0uymZ2><{9 diff --git a/src/lib/adafruit_display_text/scrolling_label.py b/src/lib/adafruit_display_text/scrolling_label.py deleted file mode 100644 index 38423a9..0000000 --- a/src/lib/adafruit_display_text/scrolling_label.py +++ /dev/null @@ -1,168 +0,0 @@ -# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_display_text.scrolling_label` -==================================================== - -Displays text into a fixed-width label that scrolls leftward -if the full_text is large enough to need it. - -* Author(s): Tim Cocks - -Implementation Notes --------------------- - -**Hardware:** - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the supported boards: - https://circuitpython.org/downloads - -""" - -<<<<<<< HEAD -__version__ = "3.1.0" -======= -<<<<<<< HEAD -__version__ = "3.2.0" -======= -__version__ = "3.1.0" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" - -import adafruit_ticks -from adafruit_display_text import bitmap_label - -try: - from typing import Optional - from fontio import FontProtocol -except ImportError: - pass - - -class ScrollingLabel(bitmap_label.Label): - """ScrollingLabel - A fixed-width label that will scroll to the left - in order to show the full text if it's larger than the fixed-width. - - :param font: The font to use for the label. - :type: ~fontio.FontProtocol - :param int max_characters: The number of characters that sets the fixed-width. Default is 10. - :param str text: The full text to show in the label. If this is longer than - ``max_characters`` then the label will scroll to show everything. - :param float animate_time: The number of seconds in between scrolling animation - frames. Default is 0.3 seconds. - :param int current_index: The index of the first visible character in the label. - Default is 0, the first character. Will increase while scrolling.""" - - # pylint: disable=too-many-arguments - def __init__( - self, - font: FontProtocol, - max_characters: int = 10, - text: Optional[str] = "", - animate_time: Optional[float] = 0.3, - current_index: Optional[int] = 0, - **kwargs - ) -> None: - super().__init__(font, **kwargs) - self.animate_time = animate_time - self._current_index = current_index - self._last_animate_time = -1 - self.max_characters = max_characters - - if text and text[-1] != " ": - text = "{} ".format(text) - self._full_text = text - - self.update() - - def update(self, force: bool = False) -> None: - """Attempt to update the display. If ``animate_time`` has elapsed since - previews animation frame then move the characters over by 1 index. - Must be called in the main loop of user code. - - :param bool force: whether to ignore ``animation_time`` and force the update. - Default is False. - :return: None - """ - _now = adafruit_ticks.ticks_ms() - if force or adafruit_ticks.ticks_less( - self._last_animate_time + int(self.animate_time * 1000), _now - ): - if len(self.full_text) <= self.max_characters: - if self._text != self.full_text: - super()._set_text(self.full_text, self.scale) - self._last_animate_time = _now - return - - if self.current_index + self.max_characters <= len(self.full_text): - _showing_string = self.full_text[ - self.current_index : self.current_index + self.max_characters - ] - else: - _showing_string_start = self.full_text[self.current_index :] - _showing_string_end = "{}".format( - self.full_text[ - : (self.current_index + self.max_characters) - % len(self.full_text) - ] - ) - - _showing_string = "{}{}".format( - _showing_string_start, _showing_string_end - ) - super()._set_text(_showing_string, self.scale) - self.current_index += 1 - self._last_animate_time = _now - - return - - @property - def current_index(self) -> int: - """Index of the first visible character. - - :return int: The current index - """ - return self._current_index - - @current_index.setter - def current_index(self, new_index: int) -> None: - if self.full_text: - self._current_index = new_index % len(self.full_text) - else: - self._current_index = 0 - - @property - def full_text(self) -> str: - """The full text to be shown. If it's longer than ``max_characters`` then - scrolling will occur as needed. - - :return str: The full text of this label. - """ - return self._full_text - - @full_text.setter - def full_text(self, new_text: str) -> None: - if new_text and new_text[-1] != " ": - new_text = "{} ".format(new_text) - if new_text != self._full_text: - self._full_text = new_text - self.current_index = 0 - self.update(True) - - @property - def text(self): - """The full text to be shown. If it's longer than ``max_characters`` then - scrolling will occur as needed. - - :return str: The full text of this label. - """ - return self.full_text - - @text.setter - def text(self, new_text): - self.full_text = new_text diff --git a/src/lib/adafruit_display_text/text_box.py b/src/lib/adafruit_display_text/text_box.py deleted file mode 100644 index 58f668b..0000000 --- a/src/lib/adafruit_display_text/text_box.py +++ /dev/null @@ -1,435 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_display_text.text_box` -================================================================================ - -Text graphics handling for CircuitPython, including text boxes - - -* Author(s): Tim Cocks - -Implementation Notes --------------------- - -**Hardware:** - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the supported boards: - https://circuitpython.org/downloads - -""" - -__version__ = "3.2.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" - -import displayio -from micropython import const - -from adafruit_display_text import wrap_text_to_pixels -from adafruit_display_text import bitmap_label - -try: - from typing import Optional, Tuple - from fontio import FontProtocol -except ImportError: - pass - - -# pylint: disable=too-many-instance-attributes, duplicate-code -class TextBox(bitmap_label.Label): - """ - TextBox has a constrained width and optionally height. - You set the desired size when it's initialized it - will automatically wrap text to fit it within the allotted - size. - - Left, Right, and Center alignment of the text within the - box are supported. - - :param font: The font to use for the TextBox. - :param width: The width of the TextBox in pixels. - :param height: The height of the TextBox in pixels. - :param align: How to align the text within the box, - valid values are ``ALIGN_LEFT``, ``ALIGN_CENTER``, ``ALIGN_RIGHT``. - """ - - ALIGN_LEFT = const(0) - ALIGN_CENTER = const(1) - ALIGN_RIGHT = const(2) - - DYNAMIC_HEIGHT = const(-1) - - def __init__( - self, font: FontProtocol, width: int, height: int, align=ALIGN_LEFT, **kwargs - ) -> None: - self._bitmap = None - self._tilegrid = None - self._prev_label_direction = None - self._width = width - - if height != TextBox.DYNAMIC_HEIGHT: - self._height = height - self.dynamic_height = False - else: - self.dynamic_height = True - - if align not in (TextBox.ALIGN_LEFT, TextBox.ALIGN_CENTER, TextBox.ALIGN_RIGHT): - raise ValueError( - "Align must be one of: ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT" - ) - self._align = align - - self._padding_left = kwargs.get("padding_left", 0) - self._padding_right = kwargs.get("padding_right", 0) - - self.lines = wrap_text_to_pixels( - kwargs.get("text", ""), - self._width - self._padding_left - self._padding_right, - font, - ) - - super(bitmap_label.Label, self).__init__(font, **kwargs) - - print(f"before reset: {self._text}") - - self._text = "\n".join(self.lines) - self._text = self._replace_tabs(self._text) - self._original_text = self._text - - # call the text updater with all the arguments. - self._reset_text( - font=font, - text=self._text, - line_spacing=self._line_spacing, - scale=self.scale, - ) - print(f"after reset: {self._text}") - - def _place_text( - self, - bitmap: displayio.Bitmap, - text: str, - font: FontProtocol, - xposition: int, - yposition: int, - skip_index: int = 0, # set to None to write all pixels, other wise skip this palette index - # when copying glyph bitmaps (this is important for slanted text - # where rectangular glyph boxes overlap) - ) -> Tuple[int, int, int, int]: - # pylint: disable=too-many-arguments, too-many-locals, too-many-statements, too-many-branches - - # placeText - Writes text into a bitmap at the specified location. - # - # Note: scale is pushed up to Group level - original_xposition = xposition - cur_line_index = 0 - cur_line_width = self._text_bounding_box(self.lines[0], self.font)[0] - - if self.align == self.ALIGN_LEFT: - x_start = original_xposition # starting x position (left margin) - if self.align == self.ALIGN_CENTER: - unused_space = self._width - cur_line_width - x_start = original_xposition + unused_space // 2 - if self.align == self.ALIGN_RIGHT: - unused_space = self._width - cur_line_width - x_start = original_xposition + unused_space - self._padding_right - - xposition = x_start # pylint: disable=used-before-assignment - - y_start = yposition - # print(f"start loc {x_start}, {y_start}") - - left = None - right = x_start - top = bottom = y_start - line_spacing = self._line_spacing - - # print(f"cur_line width: {cur_line_width}") - for char in text: - if char == "\n": # newline - cur_line_index += 1 - cur_line_width = self._text_bounding_box( - self.lines[cur_line_index], self.font - )[0] - # print(f"cur_line width: {cur_line_width}") - if self.align == self.ALIGN_LEFT: - x_start = original_xposition # starting x position (left margin) - if self.align == self.ALIGN_CENTER: - unused_space = self._width - cur_line_width - x_start = original_xposition + unused_space // 2 - if self.align == self.ALIGN_RIGHT: - unused_space = self._width - cur_line_width - x_start = original_xposition + unused_space - self._padding_right - xposition = x_start - - yposition = yposition + self._line_spacing_ypixels( - font, line_spacing - ) # Add a newline - - else: - my_glyph = font.get_glyph(ord(char)) - - if my_glyph is None: # Error checking: no glyph found - print("Glyph not found: {}".format(repr(char))) - else: - if xposition == x_start: - if left is None: - left = 0 - else: - left = min(left, my_glyph.dx) - - right = max( - right, - xposition + my_glyph.shift_x, - xposition + my_glyph.width + my_glyph.dx, - ) - if yposition == y_start: # first line, find the Ascender height - top = min(top, -my_glyph.height - my_glyph.dy) - bottom = max(bottom, yposition - my_glyph.dy) - - glyph_offset_x = ( - my_glyph.tile_index * my_glyph.width - ) # for type BuiltinFont, this creates the x-offset in the glyph bitmap. - # for BDF loaded fonts, this should equal 0 - - y_blit_target = yposition - my_glyph.height - my_glyph.dy - - # Clip glyph y-direction if outside the font ascent/descent metrics. - # Note: bitmap.blit will automatically clip the bottom of the glyph. - y_clip = 0 - if y_blit_target < 0: - y_clip = -y_blit_target # clip this amount from top of bitmap - y_blit_target = 0 # draw the clipped bitmap at y=0 - if self._verbose: - print( - 'Warning: Glyph clipped, exceeds Ascent property: "{}"'.format( - char - ) - ) - - if (y_blit_target + my_glyph.height) > bitmap.height: - if self._verbose: - print( - 'Warning: Glyph clipped, exceeds descent property: "{}"'.format( - char - ) - ) - try: - self._blit( - bitmap, - max(xposition + my_glyph.dx, 0), - y_blit_target, - my_glyph.bitmap, - x_1=glyph_offset_x, - y_1=y_clip, - x_2=glyph_offset_x + my_glyph.width, - y_2=my_glyph.height, - skip_index=skip_index, # do not copy over any 0 background pixels - ) - except ValueError: - # ignore index out of bounds error - break - - xposition = xposition + my_glyph.shift_x - - # bounding_box - return left, top, right - left, bottom - top - - def _reset_text( - self, - font: Optional[FontProtocol] = None, - text: Optional[str] = None, - line_spacing: Optional[float] = None, - scale: Optional[int] = None, - ) -> None: - # pylint: disable=too-many-branches, too-many-statements, too-many-locals - - # Store all the instance variables - if font is not None: - self._font = font - if line_spacing is not None: - self._line_spacing = line_spacing - - # if text is not provided as a parameter (text is None), use the previous value. - if text is None: - text = self._text - - self._text = self._replace_tabs(text) - print(f"inside reset_text text: {text}") - - # Check for empty string - if (text == "") or ( - text is None - ): # If empty string, just create a zero-sized bounding box and that's it. - self._bounding_box = ( - 0, - 0, - 0, # zero width with text == "" - 0, # zero height with text == "" - ) - # Clear out any items in the self._local_group Group, in case this is an - # update to the bitmap_label - for _ in self._local_group: - self._local_group.pop(0) - - # Free the bitmap and tilegrid since they are removed - self._bitmap = None - self._tilegrid = None - - else: # The text string is not empty, so create the Bitmap and TileGrid and - # append to the self Group - - # Calculate the text bounding box - - # Calculate both "tight" and "loose" bounding box dimensions to match label for - # anchor_position calculations - ( - box_x, - tight_box_y, - x_offset, - tight_y_offset, - loose_box_y, - loose_y_offset, - ) = self._text_bounding_box( - text, - self._font, - ) # calculate the box size for a tight and loose backgrounds - - if self._background_tight: - box_y = tight_box_y - y_offset = tight_y_offset - self._padding_left = 0 - self._padding_right = 0 - self._padding_top = 0 - self._padding_bottom = 0 - - else: # calculate the box size for a loose background - box_y = loose_box_y - y_offset = loose_y_offset - - # Calculate the background size including padding - tight_box_x = box_x - box_x = box_x + self._padding_left + self._padding_right - box_y = box_y + self._padding_top + self._padding_bottom - - if self.dynamic_height: - print(f"dynamic height, box_y: {box_y}") - self._height = box_y - - # Create the Bitmap unless it can be reused - new_bitmap = None - if ( - self._bitmap is None - or self._bitmap.width != self._width - or self._bitmap.height != self._height - ): - new_bitmap = displayio.Bitmap( - self._width, self._height, len(self._palette) - ) - self._bitmap = new_bitmap - else: - self._bitmap.fill(0) - - # Place the text into the Bitmap - self._place_text( - self._bitmap, - text, - self._font, - self._padding_left - x_offset, - self._padding_top + y_offset, - ) - - if self._base_alignment: - label_position_yoffset = 0 - else: - label_position_yoffset = self._ascent // 2 - - # Create the TileGrid if not created bitmap unchanged - if self._tilegrid is None or new_bitmap: - self._tilegrid = displayio.TileGrid( - self._bitmap, - pixel_shader=self._palette, - width=1, - height=1, - tile_width=self._width, - tile_height=self._height, - default_tile=0, - x=-self._padding_left + x_offset, - y=label_position_yoffset - y_offset - self._padding_top, - ) - # Clear out any items in the local_group Group, in case this is an update to - # the bitmap_label - for _ in self._local_group: - self._local_group.pop(0) - self._local_group.append( - self._tilegrid - ) # add the bitmap's tilegrid to the group - - self._bounding_box = ( - self._tilegrid.x + self._padding_left, - self._tilegrid.y + self._padding_top, - tight_box_x, - tight_box_y, - ) - print(f"end of reset_text bounding box: {self._bounding_box}") - - if ( - scale is not None - ): # Scale will be defined in local_group (Note: self should have scale=1) - self.scale = scale # call the setter - - # set the anchored_position with setter after bitmap is created, sets the - # x,y positions of the label - self.anchored_position = self._anchored_position - - @property - def height(self) -> int: - """The height of the label determined from the bounding box.""" - return self._height - - @property - def width(self) -> int: - """The width of the label determined from the bounding box.""" - return self._width - - @width.setter - def width(self, width: int) -> None: - self._width = width - self.text = self._text - - @height.setter - def height(self, height: int) -> None: - if height != TextBox.DYNAMIC_HEIGHT: - self._height = height - self.dynamic_height = False - else: - self.dynamic_height = True - self.text = self._text - - @bitmap_label.Label.text.setter - def text(self, text: str) -> None: - self.lines = wrap_text_to_pixels( - text, self._width - self._padding_left - self._padding_right, self.font - ) - self._text = self._replace_tabs(text) - self._original_text = self._text - self._text = "\n".join(self.lines) - - self._set_text(self._text, self.scale) - - @property - def align(self): - """Alignment of the text within the TextBox""" - return self._align - - @align.setter - def align(self, align: int) -> None: - if align not in (TextBox.ALIGN_LEFT, TextBox.ALIGN_CENTER, TextBox.ALIGN_RIGHT): - raise ValueError( - "Align must be one of: ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT" - ) - self._align = align diff --git a/src/lib/adafruit_ds3231.mpy b/src/lib/adafruit_ds3231.mpy deleted file mode 100644 index 474be3734f0d36d6ee32a72590372006bccc1079..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1225 zcmZXSTW{k;6vxMDnu{@w-Gm{e?$(<+VK=nN*7b%J3sSZT5>#B0vS^6}nmA*(F-qge zp6n_T4^Fh~Rv=LjPdxF$Gs?d4ZTJ|xa>h-nA{;sMJLml8T*lK(pq+OG-LkDyf8sJ? z2ZzStL3KPunaz)lX3Kf%QU|?%9q&&9Vn@!ZYtSX44bFX+q2jgBch1~^IX;q?u>ch! zqBgOHmVb`4m1$edVeYwuBsLwpqcfC%%1DG#u7_~I{0U_w5~QwVM%Anu_mI^3f;WrA zlP5<|yL{AawIJ1FNOiKReg~Y7c)9<0@xTg5;81U52S|#x!y33}BgaPSs&(?rwb=k| z4ud{9b^U-rrO$aKDR3AcZ8yh9xH=9bUyo5-5m{wIy6py5f9Mb|pzd&JG0#W2p%*YR z_MSl|%$)PF<6CUvJ19?XS`SV=pE@^P1p0pJ_;5NSBoa8LDJJyIP4mg5mthn4!Yb6ENruPAD>srO^1F&>%~pJoY^p zhfUEun}9Py^AFcQ7%(;t-mBHlTsD~WtJFKMt!_*0(dzy+U7x}|Y){$18b4fjv5K&@rp9or8_IFubv2ITz=?-Mi}2mY29h&o3}-r2 zxcs{PS>~b{mMV=(<1YO8y3hZ6?Mb6@Y!7{T%wn6X8Et3$s^i>ev^+E`?_>FhyaLpAH+ zZ@T_!k>74F^0(mK>Ck~9YG(L5h;Bdsht6dsS2V*vv|>pwmX5%?+kAMk$m<;S=EFr+ hqrJ3LGQ&T6y+yW6XIcTQdiWPlTVCXfb=2+9e*vR&RV)Ai diff --git a/src/lib/adafruit_imageload/__init__.mpy b/src/lib/adafruit_imageload/__init__.mpy deleted file mode 100644 index d2e160a105b884f4f9f11bd8779bec0b4fff8c53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 921 zcmZXRTTdE66vt;5SWyth6}MVerGgTSfz}!xnm$+-Me$PTCC#>)?ZOVYDSPQK+l2H5 zY#(a+()2s@U9{TETU&em68$P25Sw0}&dE8yIsbG1BXM9d-gi~i)CzWVqUeTNMKw#+ zQi`IRw4fyI1_%z^a_kxcaFrme5(@)(-6pzasx{zU*XyL$DN$zNwn-bnPa3vvR)Lst zDiws0b_1yz!oaOtAW%?ih!6z)8J!ra4R}qj+cmX8d$C?mvdlWcPPrpCb&CC2n)v-f zp+cw*{FjpR7nAeAUpgHbuzMQb&NaY|OQ7}GTSw>t*o|`~!v-$SHVwd4^$MU_6>JEK z@(AHN-Cj`uR1}PCi)N9EUPFq7l}e`vll8(#N??Qv@P`TBP>+5vSBz*)pHnRaZrT{osZgm-W zMo&+QsJQAF$RrVr9y&U;lmdH&O$yvL>yB+(m>@0E{lQ4Z!iGw~UT9&0xxd)-O-(-c z&duMGV%o}96SB|+-OvL$*f-0Cy&@~HqDydzZow^j1dqrGoY*Jy$xYZl%Y+BSexaY{ zKIori!-Jwv@QHrGFSlR-25AL~0U;m;g`k`txHB~IGb_n?W*ZI(lTjc}R3e(AF&bY>wzJZK zq^^uKi{ZgfZ7F*=?Ei4k3BHe1j-Tqu@zH%gw!mid{PaE?Szu2xnf;U3xkKjjQAl`_ zX1*MS&Nb7Fd>k5$GHE7Dw;!XoofpTx%5OrUBgnVm@Im@1Q|=HfCGNIhxJ!h;$!{rf ye1@=H#6^*ETgve>QTGCaT2So5;&=Jof8#v=vg2KL{D=JhFP6^Y=&Y^nV)+-JBLV>c diff --git a/src/lib/adafruit_imageload/__init__.py b/src/lib/adafruit_imageload/__init__.py deleted file mode 100644 index 02c9cca..0000000 --- a/src/lib/adafruit_imageload/__init__.py +++ /dev/null @@ -1,105 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries -# SPDX-FileCopyrightText: 2022-2023 Matt Land -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_imageload` -==================================================== - -Load pixel values (indices or colors) into a bitmap and colors into a palette. - -* Author(s): Scott Shawcroft, Matt Land - -""" - -try: - from io import BufferedReader - from typing import ( - Iterable, - Iterator, - List, - Optional, - Tuple, - Union, - ) - - from displayio import Bitmap, ColorConverter, Palette - - from .displayio_types import BitmapConstructor, PaletteConstructor -except ImportError: - pass - -<<<<<<< HEAD -__version__ = "1.20.2" -======= -<<<<<<< HEAD -__version__ = "1.23.5" -======= -__version__ = "1.20.2" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" - - -def load( - file_or_filename: Union[str, BufferedReader], - *, - bitmap: Optional[BitmapConstructor] = None, - palette: Optional[PaletteConstructor] = None, -) -> Tuple[Bitmap, Optional[Union[Palette, ColorConverter]]]: - """Load pixel values (indices or colors) into a bitmap and colors into a palette. - - bitmap is the desired type. It must take width, height and color_depth in the constructor. It - must also have a _load_row method to load a row's worth of pixel data. - - palette is the desired palette type. The constructor should take the number of colors and - support assignment to indices via []. - """ - if not bitmap or not palette: - try: - # use displayio if available - import displayio - - if not bitmap: - bitmap = displayio.Bitmap - if not palette: - palette = displayio.Palette - except ModuleNotFoundError: - # meh, we tried - pass - - if isinstance(file_or_filename, str): - open_file = open(file_or_filename, "rb") - else: - open_file = file_or_filename - - with open_file as file: - header = file.read(3) - file.seek(0) - if header.startswith(b"BM"): - from . import bmp - - return bmp.load(file, bitmap=bitmap, palette=palette) - if header.startswith(b"P"): - from . import pnm - - return pnm.load(file, header, bitmap=bitmap, palette=palette) - if header.startswith(b"GIF"): - if not bitmap: - raise RuntimeError("bitmap argument required") - - from . import gif - - return gif.load(file, bitmap=bitmap, palette=palette) - if header.startswith(b"\x89PN"): - if not bitmap: - raise RuntimeError("bitmap argument required") - from . import png - - return png.load(file, bitmap=bitmap, palette=palette) - if header.startswith(b"\xff\xd8"): - from . import jpg - - return jpg.load(file, bitmap=bitmap) - raise RuntimeError("Unsupported image format") diff --git a/src/lib/adafruit_imageload/bmp/__init__.mpy b/src/lib/adafruit_imageload/bmp/__init__.mpy deleted file mode 100644 index c44a6e7eb9f9d92ade6b70681251665f83a555a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1034 zcmZWm%Wl&^6dmW?kd{dZ<`I_~H&syy2^16|Bv46=fYL%!P$5*vb>c}f!m%xT3`A_4 zzyh&Ah$TXN0xIv6*M>)-1=bW66`#S5NmD2&n$6WY=iYPfovaV^4Y);FkxQnHiG-_i z8L7IgqzY9dB}rJLpp-OfV14k6ZKw#qQwG7hCab`A8W9i}!xjM{QZukt23*E2m5_;) zNhB-C1Rkt|o(Wk+gdh;gU{aM05K*vYsB(>R@l`IXYZfu>BGFACv}ON7+x~V))CgH1 zv?n>dJ2?zOh4#oEW7Q+j$_@e+LKi`OXZ>n@+E>73;G>a1uxuivfj~jE5r|MbRMb^! z=1+m^*_ew4WY*Ry^7faOmoKn}t}4u%VR0UOB` znY;Ybog2=`xyW`Hb_6Flm>N7am=ewlCuv2Wu&Y0@V-J0~fulnwjG38%_=(x*hMQ?7 z9}%L_+33dWX$G=s*ISC->OkL7^iBu*{#f|`<`1J=&PMu|#mV{Ofk?cc*~dID<>q(z zFVO6C!o9T#jkeoIir())KT-6-8oHWnA-6bpZAtv;JQ4Q)5WhPKu-Lr4B#MyDWZjAwTJ_q#FiD}P$#iC81^VdQh_O57pzvkbxH7}ZTR0PA1(%1dP-{CGUi2wiq diff --git a/src/lib/adafruit_imageload/bmp/__init__.py b/src/lib/adafruit_imageload/bmp/__init__.py deleted file mode 100644 index 4d33b08..0000000 --- a/src/lib/adafruit_imageload/bmp/__init__.py +++ /dev/null @@ -1,114 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries -# SPDX-FileCopyrightText: 2022-2023 Matt Land -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_imageload.bmp` -==================================================== - -Load pixel values (indices or colors) into a bitmap and colors into a palette from a BMP file. - -* Author(s): Scott Shawcroft, Matt Land - -""" - -try: - from io import BufferedReader - from typing import List, Optional, Set, Tuple, Union - - from displayio import Bitmap, ColorConverter, Palette - - from ..displayio_types import BitmapConstructor, PaletteConstructor -except ImportError: - pass - -<<<<<<< HEAD -__version__ = "1.20.2" -======= -<<<<<<< HEAD -__version__ = "1.23.5" -======= -__version__ = "1.20.2" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" - - -def load( - file: BufferedReader, - *, - bitmap: Optional[BitmapConstructor] = None, - palette: Optional[PaletteConstructor] = None, -) -> Tuple[Optional[Bitmap], Optional[Union[Palette, ColorConverter]]]: - """Loads a bmp image from the open ``file``. - - Returns tuple of `displayio.Bitmap` object and - `displayio.Palette` object, or `displayio.ColorConverter` object. - - :param io.BufferedReader file: Open file handle or compatible (like `io.BytesIO`) - with the data of a BMP file. - :param object bitmap: Type to store bitmap data. Must have API similar to `displayio.Bitmap`. - Will be skipped if None - :param object palette: Type to store the palette. Must have API similar to - `displayio.Palette`. Will be skipped if None""" - file.seek(10) - data_start = int.from_bytes(file.read(4), "little") - file.seek(14) - bmp_header_length = int.from_bytes(file.read(4), "little") - # print(bmp_header_length) - file.seek(0x12) # Width of the bitmap in pixels - _width = int.from_bytes(file.read(4), "little") - try: - _height = int.from_bytes(file.read(4), "little") - except OverflowError as error: - raise NotImplementedError( - "Negative height BMP files are not supported on builds without longint" - ) from error - file.seek(0x1C) # Number of bits per pixel - color_depth = int.from_bytes(file.read(2), "little") - file.seek(0x1E) # Compression type - compression = int.from_bytes(file.read(2), "little") - file.seek(0x2E) # Number of colors in the color palette - colors = int.from_bytes(file.read(4), "little") - bitfield_masks = None - if compression == 3 and bmp_header_length >= 56: - bitfield_masks = {} - endianess = "little" if color_depth == 16 else "big" - file.seek(0x36) - bitfield_masks["red"] = int.from_bytes(file.read(4), endianess) - file.seek(0x3A) - bitfield_masks["green"] = int.from_bytes(file.read(4), endianess) - file.seek(0x3E) - bitfield_masks["blue"] = int.from_bytes(file.read(4), endianess) - - if compression > 3: - raise NotImplementedError("bitmask compression unsupported") - - if colors == 0 and color_depth >= 16: - from . import truecolor - - return truecolor.load( - file, - _width, - _height, - data_start, - color_depth, - bitfield_masks, - bitmap=bitmap, - ) - if colors == 0: - colors = 2**color_depth - from . import indexed - - return indexed.load( - file, - _width, - _height, - data_start, - colors, - color_depth, - compression, - bitmap=bitmap, - palette=palette, - ) diff --git a/src/lib/adafruit_imageload/bmp/indexed.mpy b/src/lib/adafruit_imageload/bmp/indexed.mpy deleted file mode 100644 index 68afcda470eeab0995f41e798dc913907c8877ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1666 zcmY*XO-vg{6rQySzmBtBn~f73aO`DGz_AH+Q>iC+z5FF5!K8$2t9E1T!Cql|-Q6X` zts-`fRiR!Y^;W69wNj-X3WT2oOe{s}PZ9{qp-PZiRceKLOk)tDCpwEEN-uBT%=^Cg zz4>NBm7uBJ$V6h15ot_Ncp(*u!=xCA`G->(zmSf>n=t0fWWljYIV*$O_r@|w2=L1p zMG(`GBrqx2jF6542QxM@0wow5fRPxKK!qRzbAKcW6$JtlBPgjz1`shp&LksQRHF`R zAu%m0(pXdxCEzN%a*QF4X=K;>6LPkWHsXQ%WN0bOJD-lTnrl^<{C0Un4 zD<6Xy)LuIxYnYa`rC>^wvNr@c4k}YZ8XO5G6eT0S?f1t8B{4SaLo@mhpWXk?;roXK zDT@9V2S%O_M-5-MfB(&t8q6E4r<}B>MQ?sheaF3RP)*f-kT z=Aq9}o*qwgv%Ayn3DMNJTIKP#GitLyx9y_4)JIW1`iK*~sPGq|Y$8tmX8mYI>a1V_U^}EP&lxx~gqnd;eLehU3Dk zi|h|MZH-}9a(}PQ&3jD_7w9d+1gy!R{qhpXnaw>z$?x}>E9U|O?Y5c@Ielm|Y4B(D|z?6Yw{!|HXF@VuF;w)vM2+A5T z@)s}B_0u*pWkX{ieVU}|XSV6%Gb%GWMB6DlMefiiC>wQRaB5!P?P{g%t(O%3f<8%| zEKtWOd+ERnoa3132`0ASHvGsiwdS+o+1$pDgf|z!ej>c{Y^O^DOvsf30qo~H?Z#goKDzr@U1gr;mglw$ zrM0=j){_~Vc>&#aK^;VP{i-c!`r5m^RMLSD^u4yhhM4biD9dfI*ADOnL@2E;qr;`` z(qraJ9k@Qv_7`an<^Am!<$d|L=??RU{p%t=Btlt{ R6HD9ombRaTzP%NW{s+RbMsffE diff --git a/src/lib/adafruit_imageload/bmp/indexed.py b/src/lib/adafruit_imageload/bmp/indexed.py deleted file mode 100644 index 15ab361..0000000 --- a/src/lib/adafruit_imageload/bmp/indexed.py +++ /dev/null @@ -1,273 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries -# SPDX-FileCopyrightText: 2022-2023 Matt Land -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_imageload.bmp.indexed` -==================================================== - -Load pixel values (indices or colors) into a bitmap and colors into a palette from an indexed BMP. - -* Author(s): Scott Shawcroft, Matt Land - -""" - -import sys - -try: - from io import BufferedReader - from typing import Optional, Tuple - - from displayio import Bitmap, Palette - - from ..displayio_types import BitmapConstructor, PaletteConstructor -except ImportError: - pass - -try: - from bitmaptools import readinto as _bitmap_readinto -except ImportError: - _bitmap_readinto = None - - -<<<<<<< HEAD -__version__ = "1.20.2" -======= -<<<<<<< HEAD -__version__ = "1.23.5" -======= -__version__ = "1.20.2" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" - - -def load( # noqa: PLR0913, PLR0912, Too many arguments in function definition, Too many branches - file: BufferedReader, - width: int, - height: int, - data_start: int, - colors: int, - color_depth: int, - compression: int, - *, - bitmap: Optional[BitmapConstructor] = None, - palette: Optional[PaletteConstructor] = None, -) -> Tuple[Optional[Bitmap], Optional[Palette]]: - """Loads indexed bitmap data into bitmap and palette objects. - - :param file file: The open bmp file - :param int width: Image width in pixels - :param int height: Image height in pixels - :param int data_start: Byte location where the data starts (after headers) - :param int colors: Number of distinct colors in the image - :param int color_depth: Number of bits used to store a value - :param int compression: 0 - none, 1 - 8bit RLE, 2 - 4bit RLE - :param BitmapConstructor bitmap: a function that returns a displayio.Bitmap - :param PaletteConstructor palette: a function that returns a displayio.Palette - """ - palette_obj = None - if palette: - palette_obj = palette(colors) - - file.seek(data_start - colors * 4) - for value in range(colors): - c_bytes = file.read(4) - # Need to swap red & blue bytes (bytes 0 and 2) - palette_obj[value] = bytes( - b"".join([c_bytes[2:3], c_bytes[1:2], c_bytes[0:1], c_bytes[3:1]]) - ) - - bitmap_obj = None - if bitmap: - minimum_color_depth = 1 - while colors > 2**minimum_color_depth: - minimum_color_depth *= 2 - - if sys.maxsize > 1073741823: - from .negative_height_check import negative_height_check - - # convert unsigned int to signed int when height is negative - height = negative_height_check(height) - bitmap_obj = bitmap(width, abs(height), colors) - file.seek(data_start) - line_size = width // (8 // color_depth) - if width % (8 // color_depth) != 0: - line_size += 1 - if line_size % 4 != 0: - line_size += 4 - line_size % 4 - - mask = (1 << minimum_color_depth) - 1 - if height > 0: - range1 = height - 1 - range2 = -1 - range3 = -1 - else: - range1 = 0 - range2 = abs(height) - range3 = 1 - - if compression == 0: - if _bitmap_readinto: - _bitmap_readinto( - bitmap_obj, - file, - bits_per_pixel=color_depth, - element_size=4, - reverse_pixels_in_element=True, - reverse_rows=True, - ) - - else: # use the standard file.readinto - chunk = bytearray(line_size) - for y in range(range1, range2, range3): - file.readinto(chunk) - pixels_per_byte = 8 // color_depth - offset = y * width - - for x in range(width): - i = x // pixels_per_byte - pixel = (chunk[i] >> (8 - color_depth * (x % pixels_per_byte + 1))) & mask - bitmap_obj[offset + x] = pixel - elif compression in (1, 2): - decode_rle( - bitmap=bitmap_obj, - file=file, - compression=compression, - y_range=(range1, range2, range3), - width=width, - ) - - return bitmap_obj, palette_obj - - -def decode_rle( # noqa: PLR0912 Too many branches - bitmap: Bitmap, - file: BufferedReader, - compression: int, - y_range: Tuple[int, int, int], - width: int, -) -> None: - """Helper to decode RLE images""" - - # RLE algorithm, either 8-bit (1) or 4-bit (2) - # - # Ref: http://www.fileformat.info/format/bmp/egff.htm - - is_4bit = compression == 2 - - # This will store the 2-byte run commands, which are either an - # amount to repeat and a value to repeat, or a 0x00 and command - # marker. - run_buf = bytearray(2) - - # We need to be prepared to load up to 256 pixels of literal image - # data. (0xFF is max literal length, but odd literal runs are padded - # up to an even byte count, so we need space for 256 in the case of - # 8-bit.) 4-bit images can get away with half that. - literal_buf = bytearray(128 if is_4bit else 256) - - # We iterate with numbers rather than a range because the "delta" - # command can cause us to jump forward arbitrarily in the output - # image. - # - # In theory RLE images are only stored in bottom-up scan line order, - # but we support either. - (range1, range2, range3) = y_range - y = range1 - x = 0 - - while y * range3 < range2 * range3: - offset = y * width + x - - # We keep track of how much space is left in our row so that we - # can avoid writing extra data outside of the Bitmap. While the - # reference above seems to say that the "end run" command is - # optional and that image data should wrap from one scan line to - # the next, in practice (looking at the output of ImageMagick - # and GIMP, and what Preview renders) the bitmap part of the - # image can contain data that goes beyond the image’s stated - # width that should just be ignored. For example, the 8bit RLE - # file is 15px wide but has data for 16px. - width_remaining = width - x - - file.readinto(run_buf) - - if run_buf[0] == 0: - # A repeat length of "0" is a special command. The next byte - # tells us what needs to happen. - if run_buf[1] == 0: - # end of the current scan line - y = y + range3 - x = 0 - elif run_buf[1] == 1: - # end of image - break - elif run_buf[1] == 2: - # delta command jumps us ahead in the bitmap output by - # the x, y amounts stored in the next 2 bytes. - file.readinto(run_buf) - - x = x + run_buf[0] - y = y + run_buf[1] * range3 - else: - # command values of 3 or more indicate that many pixels - # of literal (uncompressed) image data. For 8-bit mode, - # this is raw bytes, but 4-bit mode counts in nibbles. - literal_length_px = run_buf[1] - - # Inverting the value here to get round-up integer division - if is_4bit: - read_length_bytes = -(-literal_length_px // 2) - else: - read_length_bytes = literal_length_px - - # If the run has an odd length then there’s a 1-byte padding - # we need to consume but not write into the output - if read_length_bytes % 2 == 1: - read_length_bytes += 1 - - # We use memoryview to artificially limit the length of - # literal_buf so that readinto only reads the amount - # that we want. - literal_buf_mem = memoryview(literal_buf) - file.readinto(literal_buf_mem[0:read_length_bytes]) - - if is_4bit: - for i in range(0, min(literal_length_px, width_remaining)): - # Expanding the two nibbles of the 4-bit data - # into two bytes for our output bitmap. - if i % 2 == 0: - bitmap[offset + i] = literal_buf[i // 2] >> 4 - else: - bitmap[offset + i] = literal_buf[i // 2] & 0x0F - else: - # 8-bit values are just a raw copy (limited by - # what’s left in the row so we don’t overflow out of - # the buffer) - for i in range(0, min(literal_length_px, width_remaining)): - bitmap[offset + i] = literal_buf[i] - - x = x + literal_length_px - else: - # first byte was not 0, which means it tells us how much to - # repeat the next byte into the output - run_length_px = run_buf[0] - - if is_4bit: - # In 4 bit mode, we repeat the *two* values that are - # packed into the next byte. The repeat amount is based - # on pixels, not bytes, though, so if we were to repeat - # 0xab 3 times, the output pixel values would be: 0x0a - # 0x0b 0x0a (notice how it ends at 0x0a) rather than - # 0x0a 0x0b 0x0a 0x0b 0x0a 0x0b - run_values = [run_buf[1] >> 4, run_buf[1] & 0x0F] - for i in range(0, min(run_length_px, width_remaining)): - bitmap[offset + i] = run_values[i % 2] - else: - run_value = run_buf[1] - for i in range(0, min(run_length_px, width_remaining)): - bitmap[offset + i] = run_value - - x = x + run_length_px diff --git a/src/lib/adafruit_imageload/bmp/negative_height_check.mpy b/src/lib/adafruit_imageload/bmp/negative_height_check.mpy deleted file mode 100644 index f04525bdca7f043283ba3ba43cb930a279216c84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150 zcmZ=}V~}TIic3sMOe-qQEQ!y|O-xVC$xlqtPs%OO&r3~DEXgcOjn7EUOwTBZPtHh9 z&ekiaWZ>7rD#gG9QOLl~Wn^e#Zen3WNDVez{Qy0(7Ej`82*eQ5Fo8F#398?85l!Y%3lZ}>xCMYv}voAW-MjZVo3~a#se{LQ4=^0DDzZVB{LW z`dUY7@W8HgC81RVB-5!@`8Hp<%ySjK4UB>ae2kzpIVmf)WT;qH#5NeK2(r}Rx`N0; zA}<3ntM!49WxlOIo~vKW5?6-WIv?OiBw&w660t}E*e}m9Q&UrVOzAP5P6K>Ra_`8(yF3WD_!_4O z%RF1>g<2hg>U?<#SY)1G0=@T+mGz;)MS^AF^fL5?WdX*rZC(;#7CM^Yxw;Ha9Gx~f z8LCwU=%V#)p`z4*U2gf6WAo^ss!b=c9{*NpJR_-Cat|?Pj6iI$L8m$RR zy;F*m#b)%`YNKbK?Kms6%W&71x=I~RdQRK;1!zG8l7JO8nB6)60%NF_z}OM>@Cm;iaqS(-!$cfM@6ZaIUy{A$D-r9;7WY`CzF5%3);gwbzsAA26VyXiL+ zHlF5Q+4Q4@G84&{KRkQ$V^z&8yQu&j42?V0jB5A!Dgm0NbK`;F_{F6_G&mced^sQO z3!VxEW<$ZCKXlql1?Fh_Twvk!)%?Bu{ZDhhui|zmzO$YG<%e7mcWoz;@9t=M?#>P~ zhll+GNrY!EY~6U8n}a}XhsoeJZ1i?=72LxO&J(`thlhtNPjZ)fm$pq*4jS@-*2i}R#1~0~ z#_&^Le)V+{WzT<$(2jN23ls*akV)f|W8;8!Zm7&hy+ztZxhVXA9`4WnKviji!c}H* zkshH43ZsUpk^RCNHFB``8%^~7xbnu2S*lpvhW2hldv~{?zq_=1%WwWUH%qzK{-WKG z=Khl&Wn}HT3z|eh`}Cm9-Yz`9s#UA|g&TX%kNYV2^E1~NHJUY@vYm|Bb diff --git a/src/lib/adafruit_imageload/bmp/truecolor.py b/src/lib/adafruit_imageload/bmp/truecolor.py deleted file mode 100644 index ecce3c1..0000000 --- a/src/lib/adafruit_imageload/bmp/truecolor.py +++ /dev/null @@ -1,139 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries -# SPDX-FileCopyrightText: 2022-2023 Melissa LeBlanc-Williams -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_imageload.bmp.truecolor` -==================================================== - -Load pixel colors into a bitmap from an truecolor BMP and return the correct colorconverter. - -* Author(s): Melissa LeBlanc-Williams - -""" - -import sys - -try: - from io import BufferedReader - from typing import Optional, Tuple, Union - - from ..displayio_types import BitmapConstructor -except ImportError: - pass - -from displayio import Bitmap, ColorConverter, Colorspace - -<<<<<<< HEAD -__version__ = "1.20.2" -======= -<<<<<<< HEAD -__version__ = "1.23.5" -======= -__version__ = "1.20.2" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" - -bitfield_colorspaces = ( - { # 16-bit RGB555 - "mask_values": (0x00007C00, 0x000003E0, 0x0000001F), - "color_space": Colorspace.RGB555, - }, - { # 16-bit RGB565 - "mask_values": (0x0000F800, 0x000007E0, 0x0000001F), - "color_space": Colorspace.RGB565, - }, - { # 24 or 32-bit RGB888 (Alpha ignored for 32-bit) - "mask_values": (0x0000FF00, 0x00FF0000, 0xFF000000), - "color_space": Colorspace.RGB888, - }, -) - - -def bitfield_format(bitfield_mask): - """Returns the colorspace for the given bitfield mask""" - mask = (bitfield_mask["red"], bitfield_mask["green"], bitfield_mask["blue"]) - for colorspace in bitfield_colorspaces: - if colorspace["mask_values"] == mask: - return colorspace["color_space"] - return None - - -def load( # noqa: PLR0912, PLR0913, Too many branches, Too many arguments in function definition - file: BufferedReader, - width: int, - height: int, - data_start: int, - color_depth: int, - bitfield_masks: Union[dict, None], - *, - bitmap: Optional[BitmapConstructor] = None, -) -> Tuple[Optional[Bitmap], Optional[ColorConverter]]: - """Loads truecolor bitmap data into bitmap and palette objects. Due to the 16-bit limit - that the bitmap object can hold, colors will be converted to 16-bit RGB565 values. - - :param file file: The open bmp file - :param int width: Image width in pixels - :param int height: Image height in pixels - :param int data_start: Byte location where the data starts (after headers) - :param int color_depth: Number of bits used to store a value - :param dict bitfield_masks: The bitfield masks for each color if using bitfield compression - :param BitmapConstructor bitmap: a function that returns a displayio.Bitmap - """ - converter_obj = None - bitmap_obj = None - if bitmap: - # Set up a ColorConverter object and set appropriate colorspace - # to convert from based on the color depth - input_colorspace = Colorspace.RGB888 - if bitfield_masks is not None: - colorspace = bitfield_format(bitfield_masks) - if colorspace is not None: - input_colorspace = colorspace - else: - raise NotImplementedError("Bitfield mask not supported") - elif color_depth == 16: - input_colorspace = Colorspace.RGB555 - converter_obj = ColorConverter(input_colorspace=input_colorspace) - if sys.maxsize > 1073741823: - from .negative_height_check import negative_height_check - - # convert unsigned int to signed int when height is negative - height = negative_height_check(height) - bitmap_obj = bitmap(width, abs(height), 65535) - file.seek(data_start) - line_size = width * (color_depth // 8) - # Set the seek direction based on whether the height value is negative or positive - if height > 0: - range1 = height - 1 - range2 = -1 - range3 = -1 - else: - range1 = 0 - range2 = abs(height) - range3 = 1 - chunk = bytearray(line_size) - for y in range(range1, range2, range3): - file.readinto(chunk) - bytes_per_pixel = color_depth // 8 - offset = y * width - - for x in range(width): - i = x * bytes_per_pixel - if bitfield_masks is not None: - color = 0 - for byte in range(bytes_per_pixel): - color |= chunk[i + byte] << (8 * byte) - mask = bitfield_masks["red"] | bitfield_masks["green"] | bitfield_masks["blue"] - if color_depth in (24, 32): - mask = mask >> 8 - pixel = color & mask - elif color_depth == 16: - pixel = chunk[i] | chunk[i + 1] << 8 - else: - pixel = chunk[i + 2] << 16 | chunk[i + 1] << 8 | chunk[i] - bitmap_obj[offset + x] = converter_obj.convert(pixel) - - return bitmap_obj, ColorConverter(input_colorspace=Colorspace.RGB565) diff --git a/src/lib/adafruit_imageload/displayio_types.mpy b/src/lib/adafruit_imageload/displayio_types.mpy deleted file mode 100644 index 24ab0e58d2d3c1d4369cce12635c884e30d1f0c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 331 zcmYk1!Apcd6vjtaw{5dcJIrN;VB3?sY3(s6s_9TtumwvP#5Aj>1L_Pq2Hxz_zuCXi zNNvQsy!Y{akMD>2;9#WqBqDERO$DYoN#cx=$ct#jGty9oMZ@FD*L&SGFl zkroLUElCy&A%!a{a>Bv>Uk(^2?&8>8fRD53uM4rF5@j6J4xco5cnL-q4L@q zllkM(Gn(F7BQI@AM8wFWcA(j0XKx4Wa8S5WwAs@EcjsUH>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" diff --git a/src/lib/adafruit_imageload/gif.mpy b/src/lib/adafruit_imageload/gif.mpy deleted file mode 100644 index 3b2c1a31afb28eada9a46a913d411f9f386bd8a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1712 zcmYjQT~iZh6n-~frXj>-6W0ifESoP9Ac--?k9InAiSngD6i8R6jmu_(sUeBUrWSkQ zZlx7#r_WpW4~$x6vg znolA9*<@ZqMrkISOiv+-EhG|RPK=)y`M8)vnq&rQH-Jpp-QL9MbbKTc<|Q7P&R&d$lfYVE z)q<2SjfHfU7p@}X2mSpl%OWzC19q`Qj-Linkc2d1>dDDeMz{)8M1C4sQa7&0;-Zj= zgY@b%mJ-v*xSO#NwNyTNLqrxKCGxpg9Vw>c^~g|t&jSq}Hl#GpoFikg&&6CG78i>l zJO&f98Hfh-i6m^-I9Xp$MpU#hU-vP6ysAb?PJLz8*E%avZO;`+%H~h`z#-{MVbUvP zrhR+<`99h+vNxF%z|hed=?aV*sM>lKR^f$1NT=%?ICJ7SzZ;z7k=8Vvk!YTVJY6HA zJ+!-3n$JxYrp2^G=frD;B-jdImXFi5EzwmgkoM3(8g5037r=iy4W06ZY&MgV0IWmk zuCdFHUG9+^&M+T#1upg4<9GeD2WJge={s&Y(xM_&{i8Cj#x=MW*LgH8`zST3rZl96 z(vn(AN9s5k*L$#*_b5H7huDBOcvLM$%0L>Z2C{)u@J8GOnf+8F*+`j46E|pxHaTC1 zd$~dE6C5X9Hbiz@=t2>^Y4Ru{Edh+Q22^D8SU`;vP41wYB*p?7+#FPq=75%Li3qyy zvp@bYC-<-xPiK>Cmkq8?mvh8zb9FeJV~nShc01gTfi5TODOwJE6T(*JZJ15ft@`_q za39fSQwK4TP+3U)Ae3*1u#n2HeX{y=ZEb;3(6;7L>*I?rd%YaTvXS)#M$T@}zELXAOZV<8k9L%|LG=rODj`fB z|9$C~&F}zWEj|vaI5xTT^JaK_ueL^biRNMu3u0^R$P)AZW|(CMfu$Cvvr9K0FM3qM zxBd;>YxQ)>BQ)rAxjUSGH^X=xopuLPQ*sEp-Ki^CUI8t@rl+c;kMP-a&}dKDr@MOA zewEpw=xSZcYF&t*`K~NPNvOj+m8)|*=xKv)O%s;GnR1=HJ*81YT zSK%}A`a@-FgKcMs=8E?+2QE&UUrJB&+Uc<9*78E zARRH6ZSs>xaDog(gs(IRH_u5uv-c)Q6Sl5f(Ej(XnQRz*^PG&7Ni zS2Yn4?&7SsHFRA5nszyDE{DtG8}4=l=wp3udkH#&k@ar2eFD6!y_%6_6 zgt-KC>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" - - -def load( - file: BufferedReader, *, bitmap: BitmapConstructor, palette: Optional[PaletteConstructor] = None -) -> Tuple[Bitmap, Optional[Palette]]: - """Loads a GIF image from the open ``file``. - - Returns tuple of bitmap object and palette object. - - :param io.BufferedReader file: Open file handle or compatible (like `io.BytesIO`) - with the data of a GIF file. - :param object bitmap: Type to store bitmap data. Must have API similar to `displayio.Bitmap`. - :param object palette: Type to store the palette. Must have API similar to - `displayio.Palette`. Will be skipped if None. - """ - header = file.read(6) - if header not in {b"GIF87a", b"GIF89a"}: - raise ValueError("Not a GIF file") - width, height, flags, _, _ = struct.unpack("> 4) + 1 - bitmap_obj = bitmap(width, height, (1 << color_bits) - 1) - while True: - block_type = file.read(1)[0] - if block_type == 0x2C: # frame - _read_frame(file, bitmap_obj) - elif block_type == 0x21: # extension - _ = file.read(1)[0] - # 0x01 = label, 0xfe = comment - _ = bytes(_read_blockstream(file)) - elif block_type == 0x3B: # terminator - break - else: - raise ValueError("Bad block type") - return bitmap_obj, palette_obj - - -def _read_frame(file: BufferedReader, bitmap: Bitmap) -> None: - """Read a single frame and apply it to the bitmap.""" - ddx, ddy, width, _, flags = struct.unpack("= width: - x = 0 - y += 1 - - -def _read_blockstream(file: BufferedReader) -> Iterator[int]: - """Read a block from a file.""" - while True: - size = file.read(1)[0] - if size == 0: - break - for _ in range(size): - yield file.read(1)[0] - - -class EndOfData(Exception): - """Signified end of compressed data.""" - - -class LZWDict: - """A dictionary of LZW codes.""" - - def __init__(self, code_size: int) -> None: - self.code_size = code_size - self.clear_code = 1 << code_size - self.end_code = self.clear_code + 1 - self.codes = [] # type: List[bytes] - self.last = b"" - self.clear() - - def clear(self) -> None: - """Reset the dictionary to default codes.""" - self.last = b"" - self.code_len = self.code_size + 1 - self.codes[:] = [] - - def decode(self, code: int) -> bytes: - """Decode a code.""" - if code == self.clear_code: - self.clear() - return b"" - if code == self.end_code: - raise EndOfData() - if code < self.clear_code: - value = bytes([code]) - elif code <= len(self.codes) + self.end_code: - value = self.codes[code - self.end_code - 1] - else: - value = self.last + self.last[0:1] - if self.last: - self.codes.append(self.last + value[0:1]) - if len(self.codes) + self.end_code + 1 >= 1 << self.code_len and self.code_len < 12: - self.code_len += 1 - self.last = value - return value - - -def lzw_decode(data: Iterator[int], code_size: int) -> Iterator[bytes]: - """Decode LZW-compressed data.""" - dictionary = LZWDict(code_size) - bit = 0 - try: - byte = next(data) - try: - while True: - code = 0 - for i in range(dictionary.code_len): - code |= ((byte >> bit) & 0x01) << i - bit += 1 - if bit >= 8: - bit = 0 - byte = next(data) - yield dictionary.decode(code) - except EndOfData: - while True: - next(data) - except StopIteration: - pass diff --git a/src/lib/adafruit_imageload/jpg.py b/src/lib/adafruit_imageload/jpg.py deleted file mode 100644 index 0f5d07d..0000000 --- a/src/lib/adafruit_imageload/jpg.py +++ /dev/null @@ -1,57 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Channing Ramos -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_imageload.jpg` -==================================================== - -Load a JPG into a bitmap by calling the jpegio class. - -* Author(s): Channing Ramos - -""" - -# A separate try for jpegio. Not every board supports it and this import may fail. -# If that happens an ImportError with a proper message needs to be raised -try: - from jpegio import JpegDecoder -except ImportError: - print("jpegio not supported on this board.") - -try: - from io import BufferedReader - from typing import Optional, Tuple - - from .displayio_types import BitmapConstructor -except ImportError: - pass - -from displayio import Bitmap, ColorConverter, Colorspace - -__version__ = "1.23.5" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" - - -def load( - file: BufferedReader, - *, - bitmap: BitmapConstructor, -) -> Tuple[Bitmap, Optional[ColorConverter]]: - """ - Loads a JPG image from the open ''file''. - The JPG must be a Baseline JPG, Progressive and Lossless JPG formats are not supported. - - Returns tuple of bitmap object and ColorConverter object. - - :param io.BufferedReader file: Open file handle or compatible (like 'io.BytesIO') - :param object bitmap: Type to store bitmap data. - Must have API similar to 'displayio.Bitmap'. Will be skipped if None. - Will be skipped if None. - """ - decoder = JpegDecoder() - width, height = decoder.open(file) - bitmap_obj = bitmap(width, height, 65535) - decoder.decode(bitmap_obj) - - return bitmap_obj, ColorConverter(input_colorspace=Colorspace.RGB565_SWAPPED) diff --git a/src/lib/adafruit_imageload/png.mpy b/src/lib/adafruit_imageload/png.mpy deleted file mode 100644 index 76f0cb74c8120280015804cbe897a0f00f8fc0f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1063 zcmYjO+i%-c96ok)@zOMNT-PnD*Lktyq!*Gcgdp)SI>%d=YIMogt+)sUH$H8xCAMWd zgMb9KibQ!p;+elaa3H)4{4}k> zhS{=g=s4hQre41SZ3i_`uLH7Px1nXC6S1rZ-7NKrH+`&5w_wY(J6Cl00SHg8x~}EC zna{82?rM7}hy3#UqsyP%pJ7?I8)%l*j=PHNE5St1q8f8Z1R}&2)w!90*r6B*C+19- zQ#o|!cGE^el{4~r5I$TnTxd7d2GqFS|KV_L!)aTVX}eGZkx=#Q{3#G-_vXpz24|SY ztGc1V5BDXARgq852jSG548lXF^(HcP5Yy%U^IE5;#<2INz5dixq3~{5JS}KvWRJur zu+csiXK=+MLu7aYAC0gfCd7uBFhZgvfsg}il!>wgLnuBOBl{5&XJbr^?PvOx+1Ntz z&m}^Lbsqaoo}rHL zcoBoyK8h=XA8HS4kGw7KvG;@;pa)61M=jyU@E?ELp@_ zf4O$Ah&{aLN#_juh>DN=QlC%UpE zUD&rp@v~j~yPr@A`js0cOiX@r`;LF>&tjiQ-M+Q+`S$utaga(0R1p*DJD#-VEr>(H zkTAF-GC}aScl>Q}SV#)P{$Cfx5n;sZ%8S7j?ZNFFeo>$W2JPT{NeVhRfI8^adHqvG K5vZFJc;g=nOg=RL diff --git a/src/lib/adafruit_imageload/png.py b/src/lib/adafruit_imageload/png.py deleted file mode 100644 index ebf9bf1..0000000 --- a/src/lib/adafruit_imageload/png.py +++ /dev/null @@ -1,192 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Radomir Dopieralski -# SPDX-FileCopyrightText: 2023 Matt Land -# SPDX-FileCopyrightText: 2024 Channing Ramos -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_imageload.png` -==================================================== - -Load pixel values (indices or colors) into a bitmap and colors into a palette -from a PNG file. - -* Author(s): Radomir Dopieralski, Matt Land, Channing Ramos - -""" - -try: - from io import BufferedReader - from typing import Optional, Tuple - - from displayio import Bitmap, Palette - - from .displayio_types import BitmapConstructor, PaletteConstructor -except ImportError: - pass - -import struct -import zlib - -<<<<<<< HEAD -__version__ = "1.20.2" -======= -<<<<<<< HEAD -__version__ = "1.23.5" -======= -__version__ = "1.20.2" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" - - -def load( # noqa: PLR0912, PLR0915, Too many branches, Too many statements - file: BufferedReader, *, bitmap: BitmapConstructor, palette: Optional[PaletteConstructor] = None -) -> Tuple[Bitmap, Optional[Palette]]: - """ - Loads a PNG image from the open ``file``. - Only supports indexed color images. - - Returns tuple of bitmap object and palette object. - - :param io.BufferedReader file: Open file handle or compatible (like `io.BytesIO`) - with the data of a PNG file. - :param object bitmap: Type to store bitmap data. Must have API similar to - `displayio.Bitmap`. - :param object palette: Type to store the palette. Must have API similar to - `displayio.Palette`. Will be skipped if None. - """ - header = file.read(8) - if header != b"\x89PNG\r\n\x1a\n": - raise ValueError("Not a PNG file") - del header - data = bytearray() - pal = None - mode = None - depth = 0 - width = 0 - height = 0 - while True: - size, chunk = struct.unpack(">I4s", file.read(8)) - if chunk == b"IHDR": - ( - width, - height, - depth, - mode, - compression, - filters, - interlaced, - ) = struct.unpack(">IIBBBBB", file.read(13)) - if interlaced: - raise NotImplementedError("Interlaced images unsupported") - # compression and filters must be 0 with current spec - assert compression == 0 - assert filters == 0 - elif chunk == b"PLTE": - if palette is None: - file.seek(size, 1) - else: - if mode != 3: - raise NotImplementedError("Palette in non-indexed image") - pal_size = size // 3 - pal = palette(pal_size) - for i in range(pal_size): - pal[i] = file.read(3) - elif chunk == b"tRNS": - if size > len(pal): - raise ValueError("More transparency entries than palette entries") - trns_data = file.read(size) - for i in range(len(trns_data)): - if trns_data[i] == 0: - pal.make_transparent(i) - del trns_data - elif chunk == b"IDAT": - data.extend(file.read(size)) - elif chunk == b"IEND": - break - else: - file.seek(size, 1) # skip unknown chunks - file.seek(4, 1) # skip CRC - data_bytes = zlib.decompress(data) - unit = (1, 0, 3, 1, 2, 0, 4)[mode] - scanline = (width * depth * unit + 7) // 8 - if mode == 3: # indexed - bmp = bitmap(width, height, 1 << depth) - pixels_per_byte = 8 // depth - src = 1 - src_b = 1 - pixmask = (1 << depth) - 1 - for y in range(height): - for x in range(0, width, pixels_per_byte): - byte = data_bytes[src_b] - for pixel in range(pixels_per_byte): - bmp[x + pixel, y] = (byte >> ((pixels_per_byte - pixel - 1) * depth)) & pixmask - src_b += 1 - src += scanline + 1 - src_b = src - return bmp, pal - # RGB, RGBA or Grayscale - import displayio - - if depth != 8: - raise ValueError("Must be 8bit depth.") - pal = displayio.ColorConverter(input_colorspace=displayio.Colorspace.RGB888) - bmp = bitmap(width, height, 65536) - prev = bytearray(scanline) - line = bytearray(scanline) - for y in range(height): - src = y * (scanline + 1) - filter_ = data_bytes[src] - src += 1 - if filter_ == 0: - line[0:scanline] = data_bytes[src : src + scanline] - elif filter_ == 1: # sub - for i in range(scanline): - a = line[i - unit] if i >= unit else 0 - line[i] = (data_bytes[src] + a) & 0xFF - src += 1 - elif filter_ == 2: # up - for i in range(scanline): - b = prev[i] - line[i] = (data_bytes[src] + b) & 0xFF - src += 1 - elif filter_ == 3: # average - for i in range(scanline): - a = line[i - unit] if i >= unit else 0 - b = prev[i] - line[i] = (data_bytes[src] + ((a + b) >> 1)) & 0xFF - src += 1 - elif filter_ == 4: # paeth - for i in range(scanline): - a = line[i - unit] if i >= unit else 0 - b = prev[i] - c = prev[i - unit] if i >= unit else 0 - p = a + b - c - pa = abs(p - a) - pb = abs(p - b) - pc = abs(p - c) - if pa <= pb and pa <= pc: - p = a - elif pb <= pc: - p = b - else: - p = c - line[i] = (data_bytes[src] + p) & 0xFF - src += 1 - else: - raise ValueError("Wrong filter.") - prev, line = line, prev - if mode in (0, 4): # grayscale - for x in range(width): - c = line[x * unit] - bmp[x, y] = pal.convert((c << 16) | (c << 8) | c) - elif mode in {2, 6}: # rgb - for x in range(width): - bmp[x, y] = pal.convert( - (line[x * unit + 0] << 16) | (line[x * unit + 1] << 8) | line[x * unit + 2] - ) - else: - raise ValueError("Unsupported color mode.") - pal = displayio.ColorConverter(input_colorspace=displayio.Colorspace.RGB565) - return bmp, pal diff --git a/src/lib/adafruit_imageload/pnm/__init__.mpy b/src/lib/adafruit_imageload/pnm/__init__.mpy deleted file mode 100644 index d372fff296efea44f075dc54dc7f130b9791ca8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1211 zcmZuv&2QUe9Dbb-$4%4rUFv!tbyqKL8k%HDm!&zNfs)v0tSzD$3zM0U>m*KNNMg@+ zkfJfk4j{peJAVMzu1yMTPzh-pAKSQa%38Iy;-rw{4Fo%Q1 z^b{}Eq`KZwOi^h`J91N%YH6+AN{gb>MxdC|x?m=FqoXxt0I!*{E}5zh;M%M9HWs3Lu@dqO>Kw3wp7zR(V07bWGTH>=nDvwM*ocCYrjcwzSuQ zFIoj}QKI;ftQ%-@Q3OyFby-tUM${E&1C7zdHfGvxz21p+^AsY-OCV=7h^m$T{JX6SBZxC@`bxnd*z} zF^a+H^6id-Zot%4oiQ5-ahArYbq3ws0b7#ExJ~2UqoX5$1K?jMw~dabsk({Yxj)IV z@XiJOF2FGseL?WW<`o9B0@h??@%Hw1GINVts=ZmUpa*)P5Bebv1CWRZ<|k+m<)OWl zm-bOU+E4juoWkh<6`%=<5G)vsWAl@AkP4za1gGMj`Dr>th3F}2O6b8E2oZ9Mo}p%F zNI_vec=J^B51td&vG-w^T4n(idnXA>aJEu{WaToT=1NuUtK|JJKezH9&%LrTOIjB# zA{Jw#`FMOo+s-d<@N8=w*VxD#i3l5wuDGBZV#~?pNG8UuUA6 zc!rE*z(E#so}rbx`z-PYhkrgUetj3+&0Z}KY|8p90d~cWi67z8y?@hWajp6v8m430 diff --git a/src/lib/adafruit_imageload/pnm/__init__.py b/src/lib/adafruit_imageload/pnm/__init__.py deleted file mode 100644 index a3736a0..0000000 --- a/src/lib/adafruit_imageload/pnm/__init__.py +++ /dev/null @@ -1,146 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries -# SPDX-FileCopyrightText: 2022-2023 Matt Land -# SPDX-FileCopyrightText: Brooke Storm -# SPDX-FileCopyrightText: Sam McGahan -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_imageload.pnm` -==================================================== - -Load pixel values (indices or colors) into a bitmap and colors into a palette. - -* Author(s): Matt Land, Brooke Storm, Sam McGahan - -""" - -try: - from io import BufferedReader - from typing import ( - Callable, - Iterable, - Iterator, - List, - Optional, - Tuple, - Union, - ) - - from displayio import Bitmap, Palette - - from ..displayio_types import BitmapConstructor, PaletteConstructor -except ImportError: - pass - -<<<<<<< HEAD -__version__ = "1.20.2" -======= -<<<<<<< HEAD -__version__ = "1.23.5" -======= -__version__ = "1.20.2" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" - - -def load( # noqa: PLR0912 Too many branches - file: BufferedReader, - header: bytes, - *, - bitmap: Optional[BitmapConstructor] = None, - palette: Optional[PaletteConstructor] = None, -) -> Tuple[Optional[Bitmap], Optional[Palette]]: - """ - Scan for netpbm format info, skip over comments, and delegate to a submodule - to do the actual data loading. - Formats P1, P4 have two space padded pieces of information: width and height. - All other formats have three: width, height, and max color value. - This load function will move the file stream pointer to the start of data in all cases. - """ - magic_number = header[:2] - file.seek(2) - pnm_header = [] # type: List[int] - next_value = bytearray() - while True: - # We have all we need at length 3 for formats P2, P3, P5, P6 - if len(pnm_header) == 3: - if magic_number in [b"P2", b"P5"]: - from . import pgm - - return pgm.load( - file, - magic_number, - pnm_header, - bitmap=bitmap, - palette=palette, - ) - - if magic_number == b"P3": - from . import ppm_ascii - - return ppm_ascii.load( - file, - pnm_header[0], - pnm_header[1], - bitmap=bitmap, - palette=palette, - ) - - if magic_number == b"P6": - from . import ppm_binary - - return ppm_binary.load( - file, - pnm_header[0], - pnm_header[1], - bitmap=bitmap, - palette=palette, - ) - - if len(pnm_header) == 2 and magic_number in [b"P1", b"P4"]: - if not bitmap: - raise RuntimeError( - "A bitmap constructor is required for this type of pnm format file" - ) - bitmap_obj = bitmap(pnm_header[0], pnm_header[1], 1) - palette_obj = None - if palette: - palette_obj = palette(1) - palette_obj[0] = b"\xff\xff\xff" - if magic_number.startswith(b"P1"): - from . import pbm_ascii - - return pbm_ascii.load( - file, - pnm_header[0], - pnm_header[1], - bitmap=bitmap_obj, - palette=palette_obj, - ) - - from . import pbm_binary - - return pbm_binary.load( - file, - pnm_header[0], - pnm_header[1], - bitmap=bitmap_obj, - palette=palette_obj, - ) - - next_byte = file.read(1) - if next_byte == b"": - # mpy-cross does not support !r in f-string substitution, so ignore ruff rule - raise RuntimeError("Unsupported image format {!r}".format(magic_number)) # noqa: UP032, f-string - if next_byte == b"#": # comment found, seek until a newline or EOF is found - while file.read(1) not in [b"", b"\n"]: # EOF or NL - pass - elif not next_byte.isdigit(): # boundary found in header data - if next_value: - # pull values until space is found - pnm_header.append(int("".join(["%c" % char for char in next_value]))) - next_value = bytearray() # reset the byte array - else: - next_value += next_byte # push the digit into the byte array diff --git a/src/lib/adafruit_imageload/pnm/pbm_ascii.mpy b/src/lib/adafruit_imageload/pnm/pbm_ascii.mpy deleted file mode 100644 index b5aa9727cabfa1721264d0409dce933cd9ae791c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 465 zcmXw!&2G~`6h^O|Ix&e!rzMP#Y6&T+D6%TM4N@1Zh#m1$foK8I8dRByJ&8xMA~ zA~yB{o(Cu)Pf+>@s4G@ou;vAr3M$>jCtcm|+~ZZ@CRXp0hz#>769G%fh$b0{96nAR zK1c&H2^q8b43_IJraYm5o^rvmF-gD>GtR~%Fx}~JNOKxJqa>m^EU^rldnBPkP%vC3 zQo><1ViTT_ncNxMc6ZC}f_?#l@&5z;&aHNPW23z#r=y&F zE!_79KZ;W~{oFFj9pz-+y1E0N>mJQJ%30ql-Cl9L_-?5$7hlRhXMNXwuqe+@=N>B0 P4`2QCD!zaG%~Hd^ANq>o diff --git a/src/lib/adafruit_imageload/pnm/pbm_ascii.py b/src/lib/adafruit_imageload/pnm/pbm_ascii.py deleted file mode 100644 index 33ad3de..0000000 --- a/src/lib/adafruit_imageload/pnm/pbm_ascii.py +++ /dev/null @@ -1,60 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries -# SPDX-FileCopyrightText: 2022-2023 Matt Land -# SPDX-FileCopyrightText: Brooke Storm -# SPDX-FileCopyrightText: Sam McGahan -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_imageload.pnm.pbm_ascii` -==================================================== - -Load pixel values (indices or colors) into a bitmap and for an ascii ppm, -return None for pallet. - -* Author(s): Matt Land, Brooke Storm, Sam McGahan - -""" - -try: - from io import BufferedReader - from typing import Optional, Tuple - - from displayio import Bitmap, Palette -except ImportError: - pass - -<<<<<<< HEAD -__version__ = "1.20.2" -======= -<<<<<<< HEAD -__version__ = "1.23.5" -======= -__version__ = "1.20.2" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" - - -def load( - file: BufferedReader, - width: int, - height: int, - bitmap: Bitmap, - palette: Optional[Palette] = None, -) -> Tuple[Bitmap, Optional[Palette]]: - """ - Load a P1 'PBM' ascii image into the displayio.Bitmap - """ - next_byte = b"1" # just to start the iterator - for y in range(height): - x = 0 - while next_byte: - next_byte = file.read(1) - if not next_byte.isdigit(): - continue - bitmap[x, y] = 1 if next_byte == b"1" else 0 - if x == width - 1: - break - x += 1 - return bitmap, palette diff --git a/src/lib/adafruit_imageload/pnm/pbm_binary.mpy b/src/lib/adafruit_imageload/pnm/pbm_binary.mpy deleted file mode 100644 index 40bf2d094bab8fb606ad3a3a2a13666aaded47e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 615 zcmXw#T~8B16ozNFrL=V|(+Z=|O(aWAbtSu42q7jWuqB4iNd0OWVz%4vwwO8f&(RnFCUa&pf5o;Mo;*JWYFvaO2S!PLYJtLoHS zmaUU!Lnoz%S;9@r?P{b8Mxu{9r0xLtfKc3OT6F+dspDF-{z?p~V`dqAkGmhR9pAESD2Ys~3C;$Ke diff --git a/src/lib/adafruit_imageload/pnm/pbm_binary.py b/src/lib/adafruit_imageload/pnm/pbm_binary.py deleted file mode 100644 index 27c136f..0000000 --- a/src/lib/adafruit_imageload/pnm/pbm_binary.py +++ /dev/null @@ -1,82 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries -# SPDX-FileCopyrightText: 2022-2023 Matt Land -# SPDX-FileCopyrightText: Brooke Storm -# SPDX-FileCopyrightText: Sam McGahan -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_imageload.pnm.pbm_binary` -==================================================== - -Load pixel values (indices or colors) into a bitmap and for an ascii ppm, -return None for pallet. - -* Author(s): Matt Land, Brooke Storm, Sam McGahan - -""" - -try: - from io import BufferedReader - from typing import Iterator, Optional, Tuple - - from displayio import Bitmap, Palette -except ImportError: - pass - -<<<<<<< HEAD -__version__ = "1.20.2" -======= -<<<<<<< HEAD -__version__ = "1.23.5" -======= -__version__ = "1.20.2" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" - - -def load( - file: BufferedReader, - width: int, - height: int, - bitmap: Bitmap, - palette: Optional[Palette] = None, -) -> Tuple[Bitmap, Optional[Palette]]: - """ - Load a P4 'PBM' binary image into the Bitmap - """ - x = 0 - y = 0 - while True: - next_byte = file.read(1) - if not next_byte: - break # out of bits - for bit in iterbits(next_byte): - bitmap[x, y] = bit - x += 1 - if x > width - 1: - y += 1 - x = 0 - if y > height - 1: - break - return bitmap, palette - - -def iterbits(b: bytes) -> Iterator[int]: - """ - generator to iterate over the bits in a byte (character) - """ - in_char = reverse(int.from_bytes(b, "little")) - for i in range(8): - yield (in_char >> i) & 1 - - -def reverse(b: int) -> int: - """ - reverse bit order so the iterbits works - """ - b = (b & 0xF0) >> 4 | (b & 0x0F) << 4 - b = (b & 0xCC) >> 2 | (b & 0x33) << 2 - b = (b & 0xAA) >> 1 | (b & 0x55) << 1 - return b diff --git a/src/lib/adafruit_imageload/pnm/pgm/__init__.mpy b/src/lib/adafruit_imageload/pnm/pgm/__init__.mpy deleted file mode 100644 index d0e9e6b4c7a3860f66fddf122407b49a04b4f15b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 577 zcmZ9IU279T6o$`!#-C#2=O6InC+L-b#Ys{`gyArp_nqgwXF6H9zNYRGmyDu>ahpxZn0f(m+o3;c zhvP}xwwW&k`&KxGl6H`U9tHF?Py?dt@XbzY%z$&pbwX*mGFX{ zpXUaR#13Pi2-zpm6rSWqOhjH4i!sOclgUtI(qAmI*_rj*+ok}Sb5m@Rh?;)D%{U3e zAmY@8tlZn0mwS*|J0$T!B}0#zJTPPGyXNA@;OwAh3rK6qwM#|;7Yq$+)=Q+LqUfv`I@XOME?T?V zVfo7MZpYe{j`v&8N2OsOq2V3C<-Q}G_1?UFoj%wsUeB2Xfb~>n^ z!q4u*%C!dEmFO%Te4U*PzMVIuU)`e Tuple[Optional[Bitmap], Optional[Palette]]: - """ - Perform the load of Netpbm greyscale images (P2, P5) - """ - if header[2] > 256: - raise NotImplementedError("16 bit files are not supported") - width = header[0] - height = header[1] - - if magic_number == b"P2": # To handle ascii PGM files. - from . import ascii as pgm_ascii - - return pgm_ascii.load(file, width, height, bitmap=bitmap, palette=palette) - - if magic_number == b"P5": # To handle binary PGM files. - from . import binary - - return binary.load(file, width, height, bitmap=bitmap, palette=palette) - - raise NotImplementedError("Was not able to send image") diff --git a/src/lib/adafruit_imageload/pnm/pgm/ascii.mpy b/src/lib/adafruit_imageload/pnm/pgm/ascii.mpy deleted file mode 100644 index fc6293c1f509fb78481becabcae47182ff7c12a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 807 zcmZ9H%}>){9LJxh8>4#>O07k<3UhCjZD9-6MH62_H^G2!6T_@88Kvz;W4AVI7ehSQ z%E5T@;$Puu8xv48##aw+wgXbYg%bG5E15^zmR2EZ<9hOAb31^A2wO|F&!#nwwDsV0e6 zBwmziz$vSs=Q6KIh9LnTD;pJF1Gp&bn!+#GwMeIyRjayDs|$u&1EHO^zpcT&iJ}dX zbb$Uz2T(&&6ky%8#vE4O#f!G1OVVv%o$$}qWkoD%9WrrJk#$2*E81z`j0)faT9WM} zJa=T#mx%YVWHmYE1{YRIOK}8gCzI zb$ML?JcuOy(Cjwdq2tNX8^XiHxk(d4FbC$0I|5x4LL!udbWl#x$(fid4g=kki*(r< z#oTcu;Gs|wrQD>OTg1GW&vy1vUeZhXNFO)pDfIb&=}h*e_lWy zoo_yd7kdKKp}`?DI2((e4brg?ou;F)P?!!y5;RRmgESqArH7&oqBjHH<3R>|zz;KE zy+L4B27X)(gYP+(l~;+r49MQZLl{o%9|bE6%s>WyS|*5R8OUu z+sh2Hk>6a({WO=FEj+x;K*q7zAPzjW+gm34)hv8&n(QmH@Z}%eHil)#x0l5`IJk?$ zBlaGGw2^BsU&N6aYwOzeZ`rkb9JiyM*lO*091##JJ%%0}Hj@d^C{DRwV*|}!DS|yF zbo?{_`-@->?~lgJiP_=gNCOWy?Y!^}PTOyB3Qr?%-{D8v?RLV!z}C_Xn`{Zc0gzAd A82|tP diff --git a/src/lib/adafruit_imageload/pnm/pgm/ascii.py b/src/lib/adafruit_imageload/pnm/pgm/ascii.py deleted file mode 100644 index 7c597ca..0000000 --- a/src/lib/adafruit_imageload/pnm/pgm/ascii.py +++ /dev/null @@ -1,79 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries -# SPDX-FileCopyrightText: 2022-2023 Matt Land -# SPDX-FileCopyrightText: Brooke Storm -# SPDX-FileCopyrightText: Sam McGahan -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_imageload.pnm.pgm.ascii` -==================================================== - -Load pixel values (indices or colors) into a bitmap and colors into a palette. - -* Author(s): Matt Land, Brooke Storm, Sam McGahan - -""" - -try: - from io import BufferedReader - from typing import Optional, Set, Tuple - - from displayio import Bitmap, Palette - - from ...displayio_types import BitmapConstructor, PaletteConstructor -except ImportError: - pass - - -def load( - file: BufferedReader, - width: int, - height: int, - bitmap: Optional[BitmapConstructor] = None, - palette: Optional[PaletteConstructor] = None, -) -> Tuple[Optional[Bitmap], Optional[Palette]]: - """ - Load a PGM ascii file (P2) - """ - data_start = file.tell() # keep this so we can rewind - _palette_colors = set() - pixel = bytearray() - # build a set of all colors present in the file, so palette and bitmap can be constructed - while True: - byte = file.read(1) - if byte == b"": - break - if not byte.isdigit(): - int_pixel = int("".join(["%c" % char for char in pixel])) - _palette_colors.add(int_pixel) - pixel = bytearray() - pixel += byte - palette_obj = None - if palette: - palette_obj = build_palette(palette, _palette_colors) - bitmap_obj = None - if bitmap: - bitmap_obj = bitmap(width, height, len(_palette_colors)) - file.seek(data_start) - for y in range(height): - for x in range(width): - pixel = bytearray() - while True: - byte = file.read(1) - if not byte.isdigit(): - break - pixel += byte - int_pixel = int("".join(["%c" % char for char in pixel])) - bitmap_obj[x, y] = list(_palette_colors).index(int_pixel) - return bitmap_obj, palette_obj - - -def build_palette(palette_class: PaletteConstructor, palette_colors: Set[int]) -> Palette: - """ - construct the Palette, and populate it with the set of palette_colors - """ - palette = palette_class(len(palette_colors)) - for counter, color in enumerate(palette_colors): - palette[counter] = bytes([color, color, color]) - return palette diff --git a/src/lib/adafruit_imageload/pnm/pgm/binary.mpy b/src/lib/adafruit_imageload/pnm/pgm/binary.mpy deleted file mode 100644 index 99ffd7cedb4adaa69faf0448236856f37877500e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 669 zcmZXO-EY!R7{*TvV|>ZFW33K4n2a%HWGu5Gn%%(?7l_%|%&;2M9q0iX3$1O5A>Op^ z!WeJ;L-^F`yz+aS@zzVP{69R)a4|9G;yv&CJn!@V@*WVI2OCI6HKVOqlF~$VtZGQk z=&fc(uQxLlrG<>nvfcqx{=02m#emt=Ek$b~6?k^A1%g&bS6X$z=i0RzHn6;nk&F%C zRx}XWLMpZ_41zhuY9bv#Suu4Lb%>T2Yk93@T1LBSX$FX&+5R0x&o?A;2%ErriU+(F zR#o6U^&puvCdQ9|b1PhFE2=E%J7qMIYSBkwLP10Hk}El z3~{U7#0Da`ju;-RQdLE!3Hb9?Q#Hc`&lBk|Wl!3^_|5d{gX)XeM!{xi#>Kc(uE+#W zb2RVbT)dlei#Fp)QISdB!+FTx%lJ}s#Ls&3Fz)8?wWZ zKp&S*2J1iUL9Y*^BeLT<>9X)Ov6H;V|K-8hfj~(`ZiW7S1m}pKy-1=D-@i%GySZy; z`F>G6e3zoD`=A2Zt8sg6?|OQ<3!}YLq7N`es#Jt4^v6$db~qd^xdf^|*v+N;)jzJ- B#`^#O diff --git a/src/lib/adafruit_imageload/pnm/pgm/binary.py b/src/lib/adafruit_imageload/pnm/pgm/binary.py deleted file mode 100644 index 700b563..0000000 --- a/src/lib/adafruit_imageload/pnm/pgm/binary.py +++ /dev/null @@ -1,67 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries -# SPDX-FileCopyrightText: 2022-2023 Matt Land -# SPDX-FileCopyrightText: Brooke Storm -# SPDX-FileCopyrightText: Sam McGahan -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_imageload.pnm.pgm.binary` -==================================================== - -Load pixel values (indices or colors) into a bitmap and colors into a palette. - -* Author(s): Matt Land, Brooke Storm, Sam McGahan - -""" - -try: - from io import BufferedReader - from typing import Optional, Set, Tuple - - from displayio import Bitmap, Palette - - from ...displayio_types import BitmapConstructor, PaletteConstructor -except ImportError: - pass - - -def load( - file: BufferedReader, - width: int, - height: int, - bitmap: Optional[BitmapConstructor] = None, - palette: Optional[PaletteConstructor] = None, -) -> Tuple[Optional[Bitmap], Optional[Palette]]: - """ - Load a P5 format file (binary), handle PGM (greyscale) - """ - palette_colors = set() # type: Set[int] - data_start = file.tell() - for y in range(height): - data_line = iter(bytes(file.read(width))) - for pixel in data_line: - palette_colors.add(pixel) - - palette_obj = None - if palette: - palette_obj = build_palette(palette, palette_colors) - bitmap_obj = None - if bitmap: - bitmap_obj = bitmap(width, height, len(palette_colors)) - file.seek(data_start) - for y in range(height): - data_line = iter(bytes(file.read(width))) - for x, pixel in enumerate(data_line): - bitmap_obj[x, y] = list(palette_colors).index(pixel) - return bitmap_obj, palette_obj - - -def build_palette(palette_class: PaletteConstructor, palette_colors: Set[int]) -> Palette: - """ - construct the Palette, and populate it with the set of palette_colors - """ - _palette = palette_class(len(palette_colors)) - for counter, color in enumerate(palette_colors): - _palette[counter] = bytes([color, color, color]) - return _palette diff --git a/src/lib/adafruit_imageload/pnm/ppm_ascii.mpy b/src/lib/adafruit_imageload/pnm/ppm_ascii.mpy deleted file mode 100644 index fa95ee33ec4224df9461da306d8cd65c1e4daaf0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 909 zcmYjOOK%%h6h7DQ7tUlDZd)=mt(}aq9qeGHR(Q%8TNP;n)D$Hh3K}Nk8+*jqGn%;u zSuB_dLV(!v3-|?ow57Ck(N;hoAjArF#j5{-B{xlqxSRXwob#RYedjb|uviV9)jRs0 zJ2a_g_Vq5YY`s%;2K}nz^flcxOta#QU@r4wrM6nBLF&DsV-essC~^-eJ%qp}2Yus#Y0ZQ(VG={4-W^9Dqg4ZxcA32{B zJ{RU9T$GD(@ltdlA%<~SjNphE#ZfVaV`3b~Ri8_i&_YT~;v~aqE>j9EJSC=aTFl^# zy3b{~ITqo>EY6B^cuqZ++L=G~cdMzMLm#%=rO<_!0MFMD#%&|??b^NX(AxazTz+ZU zFZ;=5sko8L7gwa*QlTtK#nm-llHupEz}Mjy9*Fl+tcj|Jf>E1ylKhn3+6Q(tj+ew+A;vGe;;*JJnj zBBKk>T(6@WH-9RHwpI3&M)2t3`+qf$tLh<0s_KnvrI7Iz{j(wA)Bfg1jr{VdLSaif zQz(|3QW(Dwoojtm(^rp>?No#kOM-N*cOoS7BnR`}PsKHc5 zbta!Fe9TlDd|@J^FCMnu=2!R^7?e?0WqltLmA0XL|9RU$!RmTKtoRqpFxEDcx47pf K|5OF#jPWmx=ndik diff --git a/src/lib/adafruit_imageload/pnm/ppm_ascii.py b/src/lib/adafruit_imageload/pnm/ppm_ascii.py deleted file mode 100644 index dc711c4..0000000 --- a/src/lib/adafruit_imageload/pnm/ppm_ascii.py +++ /dev/null @@ -1,106 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries -# SPDX-FileCopyrightText: 2022-2023 Matt Land -# SPDX-FileCopyrightText: Brooke Storm -# SPDX-FileCopyrightText: Sam McGahan -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_imageload.pnm.ppm_ascii` -==================================================== - -Load pixel values (indices or colors) into a bitmap and for an ascii ppm, -return None for pallet. - -* Author(s): Matt Land, Brooke Storm, Sam McGahan - -""" - -<<<<<<< HEAD -__version__ = "1.20.2" -======= -<<<<<<< HEAD -__version__ = "1.23.5" -======= -__version__ = "1.20.2" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" - -try: - from io import BufferedReader - from typing import ( - Iterator, - List, - Optional, - Set, - Tuple, - ) - - from displayio import Bitmap, Palette - - from ..displayio_types import BitmapConstructor, PaletteConstructor -except ImportError: - pass - - -def load( - file: BufferedReader, - width: int, - height: int, - bitmap: Optional[BitmapConstructor] = None, - palette: Optional[PaletteConstructor] = None, -) -> Tuple[Optional[Bitmap], Optional[Palette]]: - """ - :param stream file: infile with the position set at start of data - :param int width: - :param int height: - :param int max_colors: color space of file - :param bitmap: displayio.Bitmap class - :param palette: displayio.Palette class - :return tuple: - """ - palette_colors = set() # type: Set[bytes] - data_start = file.tell() - for triplet in read_three_colors(file): - palette_colors.add(triplet) - - palette_obj = None - if palette: - palette_obj = palette(len(palette_colors)) - for counter, color in enumerate(palette_colors): - palette_obj[counter] = color - bitmap_obj = None - if bitmap: - file.seek(data_start) - bitmap_obj = bitmap(width, height, len(palette_colors)) - for y in range(height): - for x in range(width): - for color in read_three_colors(file): - bitmap_obj[x, y] = list(palette_colors).index(color) - break # exit the inner generator - return bitmap_obj, palette_obj - - -def read_three_colors(file: BufferedReader) -> Iterator[bytes]: - """ - Generator to read integer values from file, in groups of three. - Each value can be len 1-3, for values 0 - 255, space padded. - :return Iterator[bytes]: - """ - triplet = [] # type: List[int] - color = bytearray() - while True: - this_byte = file.read(1) - if this_byte.isdigit(): - color += this_byte - # not a digit means we completed one number (found a space separator or EOF) - elif color or (triplet and this_byte == b""): - triplet.append(int("".join(["%c" % char for char in color]))) - color = bytearray() - if len(triplet) == 3: # completed one pixel - yield bytes(tuple(triplet)) - triplet = [] - # short circuit must be after all other cases, so we yield the last pixel before returning - if this_byte == b"": - return diff --git a/src/lib/adafruit_imageload/pnm/ppm_binary.mpy b/src/lib/adafruit_imageload/pnm/ppm_binary.mpy deleted file mode 100644 index af2020337b757d40109ff6ed2488ae8049b22178..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 736 zcmZXQ&r{Pt6vsDdDW${`VqJws(6$0bra|m5>PgcwgP;QyrA9|XXtwQ+Nt;QxIvt%c z!GlLH{vrGlMHEgdg2K_!izogA{{R;Yj-#_PyKmpV_r9O~rURfm!HnpJUT|udXxP$= z$gJpwY?m$Bwk>T2mvyI}wCkWPbfadQ2=L=J!IiRZg1{sqfFpGqmy1ADYJ~!FkZ~R9 z26BKOS3r0|HxVHSa0(`tZUe! zO%4tu2SIQT69fq|P2fEW=mx!15xNh&3)i5gJwQ&CQfeCDHO)bGg+{>wru?CY*dQgq zl@KnLDE~|YEo|?z&USd?UXzEtoPf$T3pqOdfTuQHDiLB=FUoQelTvLanXg##fpPNX z0|(RC$x|mM>ZC-~uk87Gm4YT|6L^;C?_}I&HxxVHKX@mdFh)jQp5=YKU-CtpL>985 z5Bfwu^s6o(keFz*7=Qs92l*C>jfTV^42mtVMV;kad5&fdiLJ0z zD07z&Ll_6}Xf`Xclji_z%jTKqecxU%O53rH&Zy`nrl)&OWqRb{d$IWGekqnX)7ul1 zhkI{M%tiPi@Lo8c0w3m|{`|P$Dpqbo=n}f;BJHn+m=s-~R>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" - - -def load( - file: BufferedReader, - width: int, - height: int, - bitmap: Optional[BitmapConstructor] = None, - palette: Optional[PaletteConstructor] = None, -) -> Tuple[Optional[Bitmap], Optional[Palette]]: - """ - Load pixel values (indices or colors) into a bitmap and for a binary - ppm, return None for pallet. - """ - - data_start = file.tell() - palette_colors = set() # type: Set[Tuple[int, int, int]] - line_size = width * 3 - - for y in range(height): - data_line = iter(bytes(file.read(line_size))) - for red in data_line: - # red, green, blue - palette_colors.add((red, next(data_line), next(data_line))) - - palette_obj = None - if palette: - palette_obj = palette(len(palette_colors)) - for counter, color in enumerate(palette_colors): - palette_obj[counter] = bytes(color) - bitmap_obj = None - if bitmap: - bitmap_obj = bitmap(width, height, len(palette_colors)) - file.seek(data_start) - for y in range(height): - x = 0 - data_line = iter(bytes(file.read(line_size))) - for red in data_line: - # red, green, blue - bitmap_obj[x, y] = list(palette_colors).index( - (red, next(data_line), next(data_line)) - ) - x += 1 - - return bitmap_obj, palette_obj diff --git a/src/lib/adafruit_imageload/tilegrid_inflator.mpy b/src/lib/adafruit_imageload/tilegrid_inflator.mpy deleted file mode 100644 index ccaec3574db759f783f906e4208992488e40888b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1019 zcmZWmTTc{86s~UO0?mj;&ulF&t+dQQ!eC}tB|Ny1z|2HIK^S$wHZg5Mmov4@rPE!w zVD_OKRE*Ci{tYgQm)+TY^L}~o!8c7d**_qO&sNh+Vs?1ss$OYMW)eLTe!Le-V%_E}3Nl;IzqT zt%}Q_d6L=;NQEj-L4>WCv|0phGq_9`BOsEYtb$F@Fi zDU%Sj6B3vDD zq~6or!(HRqgfZ&mJ4Gb&vk5hg4?8dbgD?cc$v|hb5XA8`^6OpmE*?^JiDx38zaZ=$DnJdsP}mYr}lu zOg`ktnA{Z?2L-~>54kD1DRyvUNEi^GJ#;D?xxrB Pd)_fk^96@At-kOL=K@&C diff --git a/src/lib/adafruit_imageload/tilegrid_inflator.py b/src/lib/adafruit_imageload/tilegrid_inflator.py deleted file mode 100644 index ea931a8..0000000 --- a/src/lib/adafruit_imageload/tilegrid_inflator.py +++ /dev/null @@ -1,117 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Tim Cocks for Adafruit Industries -# SPDX-FileCopyrightText: 2022-2023 Matt Land -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_imageload.tilegrid_inflator` -==================================================== - -Use a 3x3 spritesheet to inflate a larger grid of tiles, duplicating the center rows and -columns as many times as needed to reach a target size. - -* Author(s): Tim Cocks, Matt Land - -""" - -import displayio - -import adafruit_imageload - -try: - from typing import List, Optional, Tuple, Union - - from displayio import Bitmap, OnDiskBitmap, Palette, TileGrid -except ImportError: - pass - -<<<<<<< HEAD -__version__ = "1.20.2" -======= -<<<<<<< HEAD -__version__ = "1.23.5" -======= -__version__ = "1.20.2" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" - - -def inflate_tilegrid( # noqa: PLR0913, PLR0912, Too many arguments in function definition, Too many branches - bmp_path: Optional[str] = None, - target_size: Tuple[int, int] = (3, 3), - tile_size: Optional[List[int]] = None, - transparent_index: Optional[Union[tuple, int]] = None, - bmp_obj: Optional[OnDiskBitmap] = None, - bmp_palette: Optional[Palette] = None, -) -> TileGrid: - """ - inflate a TileGrid of ``target_size`` in tiles from a 3x3 spritesheet by duplicating - the center rows and columns. - - :param Optional[str] bmp_path: filepath to the 3x3 spritesheet bitmap file - :param tuple[int, int] target_size: desired size in tiles (target_width, target_height) - :param Optional[List[int]] tile_size: size of the tiles in the 3x3 spritesheet. If - None is used it will equally divide the width and height of the Bitmap by 3. - :param Optional[Union[tuple, int]] transparent_index: a single index within the palette to - make transparent, or a tuple of multiple indexes to make transparent - :param Optional[OnDiskBitmap] bmp_obj: Already loaded 3x3 spritesheet in an OnDiskBitmap - :param Optional[Palette] bmp_palette: Already loaded spritesheet Palette - """ - - if bmp_path is None and (bmp_obj is None and bmp_palette is None): - raise AttributeError("Must pass either bmp_path or bmp_obj and bmp_palette") - - image: Bitmap - palette: Palette - if bmp_path is not None: - image, palette = adafruit_imageload.load(bmp_path) # type: ignore[assignment] - else: - image = bmp_obj # type: ignore[assignment] - palette = bmp_palette # type: ignore[assignment] - - if transparent_index is not None: - if isinstance(transparent_index, tuple): - for index in transparent_index: - palette.make_transparent(index) - elif isinstance(transparent_index, int): - palette.make_transparent(transparent_index) - - if tile_size is None: - tile_width = image.width // 3 - tile_height = image.height // 3 - else: - tile_width = tile_size[0] - tile_height = tile_size[1] - - target_width = target_size[0] - target_height = target_size[1] - - tile_grid = displayio.TileGrid( - image, - pixel_shader=palette, - height=target_height, - width=target_width, - tile_width=tile_width, - tile_height=tile_height, - ) - - # corners - tile_grid[0, 0] = 0 # upper left - tile_grid[tile_grid.width - 1, 0] = 2 # upper right - tile_grid[0, tile_grid.height - 1] = 6 # lower left - tile_grid[tile_grid.width - 1, tile_grid.height - 1] = 8 # lower right - - for x in range(target_size[0] - 2): - tile_grid[x + 1, 0] = 1 - tile_grid[x + 1, tile_grid.height - 1] = 7 - - for y in range(target_size[1] - 2): - tile_grid[0, y + 1] = 3 - tile_grid[tile_grid.width - 1, y + 1] = 5 - - for y in range(target_size[1] - 2): - for x in range(target_size[0] - 2): - tile_grid[x + 1, y + 1] = 4 - - return tile_grid diff --git a/src/lib/adafruit_irremote.mpy b/src/lib/adafruit_irremote.mpy deleted file mode 100644 index dc06539ce6f1b9fee58caeabb3ac6f5f3b54413a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2957 zcmZuxTTmO<89pl{5aQxxCDt;=c)hwHkRm_`;v`NCtF^eoj_ou~TUFPZ5>B`yLxcE$#bukCau559CJ$qUcTFpQ^;G#iNk zT{NAJUIqp(I>*L^Y%0kDoZ*v6mMQsag~S{Sv}Y3;0T@PQr-0&wj-_OWXcFj3K&@~& zmEfiUI-X5t*s-ht>Ql0m;6bh1+2!o|0FXm|nq~MnduEqWg@AY6Tmz`tN zQGo@ykEF0PQ8}03KqVvz)a3M%$C%(!Sz-ScsB%9Zj)qp@T)Lz=JtGLI%!eHv(+OcF z8*@S?I`${Hqkn&tJ&7~}M|%9SFat{)^7~7J4P$^#rKv2_rco;`I*3%3`=~?SdTG_2 z&i8w?>Mp0NM{{VD7YLT)v(qz#Qj+E<#S1e8`vuE&@bNfNN=?KQQ&W&ZjbU6K=?oF) z3HY4e!birq$s&5?2lymlrLXBDpw#Rq6BI-One()q`v z!;4JzowYi=tIBEa~)n69@b4X zocx*ggolyO&_nnKCV+n!Z~+u;_{d#>qX8F!HYj3)ONAR<6?jvF2E|N>TZLnaq=M2Z zDCtcVP|^onYSf?v7y>R0+6b}6fU6S4p@0WmRp?e}ba;X&(di zhQ$#@Z;5(c{dUnx!9@N*c{)&`ULq)r*9wTMGr|R-k}hX|jn^latmdqP@2#=xwu4*=fo)WA(OKy;i%ojWoBB zG~DghHoLcDjGiGJJ=PwtJ>hgXOpbc2AJ+K*qu_cU^T74$D|%Zbf9QiZkxQ&1#lNGa z0#oFl#`BVbR+4`{SpqMTSE+oi4F3L93G`xmcwa`z_w@_fhn*^*N6}_?4%xsAOKyZ! zEV+M+uV{K)8XF;#Z2I_SQx*vl5}|5$P_%e9-T4_N4R z*yP~41Q)mJ33le7`qWZS5B@+@APS$Q85r$UJ$rr!_WZ6w=$*G6_m1bk4^he6Z%wz~ znf$V;T$b+L;Z%M>v?B_AU+pfw+5200&W_7x-1~mcjVR?r$^vlH%bsWR6 ze%KQ97}kH!jlg`2TAJ+FTD{_RJIF^y4m~JHK}b|@K@4^ns18ONSFB$v);h)d5?Z0H zYv14GU1leM!E$PEpG9?je_8vSaeQGAuq;-jzXqS2=^W zzH#xnsGsdqz()MEJc8}W`EG!>x)8kG$Ec+7TL{`ZU~3V7F`El{qirc~Y=Pw%+kDKI zgHI@;eL&n1wI*BZEJA35N?UJzcI4#QNQzb^QgYjI->eiMjQapIcJ zeEkGBX;Es%@zX82tJM2DMld#5aZ;l@dochtwFlJhfjRHn@c8-75MF z*5C&0lKvR;<@i6YA-&Flp%&tXQPhznNsv~v#X^!Mi)k2czAf?L_%`+1f=tY#RcYtp z+R{sUXvOyvO;G{=cF{8?%N{$}S}`11gJcyv`c?R9sTD?ih*cRnmSqC(K`O4Ed@YKL zeMBcb?KpAbMxR(iBy2V^Z*UftR(dP8m+7%0%rz`Wt;$hUk)ck&K(i4$d^03ge^Wg@ zsjzn#KMjc*N6DDnaVUzYDQ2og%`qQpiFG4*51fG9;O#$%Vqa71K$olAP2Px^;SWwi z7n}swa^Bdzk~g|n^G4Fgd~5s5%|)?qv}a&=#ALSI_*|^E*(vM1&1SQbq{C{rowc=_ z&6ZJ%x7BL0VIKH}THM=S_5JiCJ+NAQK~t;SnA)I5d*Rc|6?1#LdA(bvMWp(r-MwDb zqrARL46cbGxJ$t`3GUGTBkhIz+CnJzSbT!@!K9IqP^aqYv#+QvapL*XouG90*MEsG z=|EoZg5m2h%mu^OrQHza&+EI(Sa%s)&+RIJ$F%+`3*WVyI$G z(sn_c$H00?#=wn>+Y0auOEGC^7`SE^GQeXf`i`Olw}x~KSo|8I^>stRpvyq=3k5@) zf=U>KnxTf!7VyG4!_EoC3$>gL1U$SVcfF?{9Ni0Oiw=nfI&?+eS=Z4vEUF|G zN~-M*+7-|cm;j!7Zw-MNz-}Qqw^daYAS5F&ib#bJFc9jBhR7*M<^vbz;NtAe3Y^T$ zr5=Fj;2lQOk*4T)7gRXDX0SS!GF84`UUrUdVyqdX(db4IZ35k^)7&46H22zpe)hL_8$lDU69Al+DO}`5__9`5>$EO;qqsb91oVtzgC`pPYtcET3ic% zjRa1xLwk~*9ymSZMT=vS)aoIBouDnwV{xthbdn~84pJb0Y9e2<&z`e1-^p~5Ds!Wf ztkCA-*Q3hu-{9WQ-ygxn8F5<$bXNg&AN%K3;I|WtYdd3u=SMa4pL5b5xwxlxdA(?H JzOy7H{{uHS5pMth diff --git a/src/lib/adafruit_register/__init__.py b/src/lib/adafruit_register/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/lib/adafruit_register/i2c_bcd_alarm.mpy b/src/lib/adafruit_register/i2c_bcd_alarm.mpy deleted file mode 100644 index 882b011cd50c5d123ea1b7232c5f6ab8f898ec49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1740 zcmZ8fOLNm!6uwGgV^R}tWFri5LC6n`No+7CB<+I4mcyfY*>N1qzzA8siA7_{m1NR% zQ6v){Wz$7xy6!)yNkZDN>yQ9p+eJx-wkx{pFin^a%U)S=X4)Bz?m6H6&bjBD?q#qTtO&u{4B4OBb9lu-+4OHA35Cz9GX~00FG5OQI_~w z;Kpv+ z0)D+Rua#tpvmzqJ28Am&I4XcD5@e}3I}c!4l&TuSma`;R6@0cAdC_#|5c(9K9c5Y4 zO4tY`lI)<_Ko*yr*=Y?xv$0q~luS#MV%P&ds>G|D^@IwXh3fP)F3f8tEb{Z5TH+N{ z1c!HhjaM|#!ErN4<2c|pr)AX(f^!OPozqH4!qeo7qNK^7L#}EZl8OL|5~ntTDOFRd zg2pwbi8HBDCoea*=EC-!^Jd=vT4*VQ5=T{XpR+E0Xh6&MaC92 zlu4y0Q=`c|mjxrI28RbTBV2N9WPEHiHJatvOA~{M;S>wVT^^{VqCID8F1on@9A7Ld zNL7JB*yX}a+(F?c76t)hO17OZX<9}7C>os+wNkYZ5#(~TxkAyw<}M{IRmUw=o8yn* z7|?FF?P-c+v+u}evsp6Q>^rjB9J`v^9J`|1{5gKMis*7x)#w7ERrFaENdltfX?hk( zGg^t(=44IT zP=PH}$Ootsdb+EXYQ5O6!!~Gx_Hb+09&a1f=Cx5aubr~zb?68aU3S(Ffnn4pbp1cC>m#6aQt+aU=GC2P@Y5j6yx*z{QgkTe|LR}Wgi=Vt}mL$ zoAt%Iha%&T<3!za@KN1$P|q#ZJ)Y&d=TKVs_R9C2xx=xuy?QB_V;CmTlk!CvrpGtR zM1A87GZy!yeQMk{9t;JinFRgF)pg;(( z!HmAMn{i?{+Qb^~Zmgz%|CznGTHC(={$*nPiRleJscmf}*n2OH4YJjJl=+%BR)73q z<;m*p@ZMfxIou+A(>F6f$IeFd?@nBZ#QcHdL!t2Wr^Y^jGYJ>5h`_MSar6;Pt?w>ut0KW_2CR;V9ebu@Q-~n#M2}U3I%dE1tl}lv*b7P`+FnZMt?%ZTE a)@{&AYVrrn{o0qufA?|B?j;&~CHxCXi#~Ax diff --git a/src/lib/adafruit_register/i2c_bcd_alarm.py b/src/lib/adafruit_register/i2c_bcd_alarm.py deleted file mode 100644 index 00f245d..0000000 --- a/src/lib/adafruit_register/i2c_bcd_alarm.py +++ /dev/null @@ -1,210 +0,0 @@ -# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT -# pylint: disable=too-few-public-methods -# pylint: disable=too-many-branches - -""" -`adafruit_register.i2c_bcd_alarm` -==================================================== - -Binary Coded Decimal alarm register - -* Author(s): Scott Shawcroft -""" - -<<<<<<< HEAD -__version__ = "1.9.18" -======= -<<<<<<< HEAD -__version__ = "1.10.1" -======= -__version__ = "1.9.18" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" - -import time - -try: - from typing import Optional, Type, Tuple - from typing_extensions import Literal - from circuitpython_typing.device_drivers import I2CDeviceDriver - - FREQUENCY_T = Literal[ - "monthly", "weekly", "daily", "hourly", "minutely", "secondly" - ] -except ImportError: - pass - - -def _bcd2bin(value: int) -> int: - """Convert binary coded decimal to Binary - - :param value: the BCD value to convert to binary (required, no default) - """ - return value - 6 * (value >> 4) - - -def _bin2bcd(value: int) -> int: - """Convert a binary value to binary coded decimal. - - :param value: the binary value to convert to BCD. (required, no default) - """ - return value + 6 * (value // 10) - - -ALARM_COMPONENT_DISABLED = 0x80 -FREQUENCY = ["secondly", "minutely", "hourly", "daily", "weekly", "monthly"] - - -class BCDAlarmTimeRegister: - """ - Alarm date and time register using binary coded decimal structure. - - The byte order of the registers must* be: [second], minute, hour, day, - weekday. Each byte must also have a high enable bit where 1 is disabled and - 0 is enabled. - - * If weekday_shared is True, then weekday and day share a register. - * If has_seconds is True, then there is a seconds register. - - Values are a tuple of (`time.struct_time`, `str`) where the struct represents - a date and time that would alarm. The string is the frequency: - - * "secondly", once a second (only if alarm has_seconds) - * "minutely", once a minute when seconds match (if alarm doesn't seconds then when seconds = 0) - * "hourly", once an hour when ``tm_min`` and ``tm_sec`` match - * "daily", once a day when ``tm_hour``, ``tm_min`` and ``tm_sec`` match - * "weekly", once a week when ``tm_wday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match - * "monthly", once a month when ``tm_mday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match - - :param int register_address: The register address to start the read - :param bool has_seconds: True if the alarm can happen minutely. - :param bool weekday_shared: True if weekday and day share the same register - :param int weekday_start: 0 or 1 depending on the RTC's representation of the first day of the - week (Monday) - """ - - # Defaults are based on alarm1 of the DS3231. - def __init__( - self, - register_address: int, - has_seconds: bool = True, - weekday_shared: bool = True, - weekday_start: Literal[0, 1] = 1, - ) -> None: - buffer_size = 5 - if weekday_shared: - buffer_size -= 1 - if has_seconds: - buffer_size += 1 - self.has_seconds = has_seconds - self.buffer = bytearray(buffer_size) - self.buffer[0] = register_address - self.weekday_shared = weekday_shared - self.weekday_start = weekday_start - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> Tuple[time.struct_time, FREQUENCY_T]: - # Read the alarm register. - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - - frequency = None - i = 1 - seconds = 0 - if self.has_seconds: - if (self.buffer[1] & 0x80) != 0: - frequency = "secondly" - else: - frequency = "minutely" - seconds = _bcd2bin(self.buffer[1] & 0x7F) - i = 2 - else: - frequency = "minutely" - seconds = _bcd2bin(self.buffer[i] & 0x7F) - minute = 0 - if (self.buffer[i] & 0x80) == 0: - frequency = "hourly" - minute = _bcd2bin(self.buffer[i] & 0x7F) - - hour = 0 - if (self.buffer[i + 1] & 0x80) == 0: - frequency = "daily" - hour = _bcd2bin(self.buffer[i + 1] & 0x7F) - - mday = None - wday = None - if (self.buffer[i + 2] & 0x80) == 0: - # day of the month - if not self.weekday_shared or (self.buffer[i + 2] & 0x40) == 0: - frequency = "monthly" - mday = _bcd2bin(self.buffer[i + 2] & 0x3F) - else: # weekday - frequency = "weekly" - wday = _bcd2bin(self.buffer[i + 2] & 0x3F) - self.weekday_start - - # weekday - if not self.weekday_shared and (self.buffer[i + 3] & 0x80) == 0: - frequency = "monthly" - mday = _bcd2bin(self.buffer[i + 3] & 0x7F) - - if mday is not None: - wday = (mday - 2) % 7 - elif wday is not None: - mday = wday + 2 - else: - # Jan 1, 2017 was a Sunday (6) - wday = 6 - mday = 1 - - return ( - time.struct_time((2017, 1, mday, hour, minute, seconds, wday, mday, -1)), - frequency, - ) - - def __set__( - self, obj: I2CDeviceDriver, value: Tuple[time.struct_time, FREQUENCY_T] - ) -> None: - if len(value) != 2: - raise ValueError("Value must be sequence of length two") - # Turn all components off by default. - for i in range(len(self.buffer) - 1): - self.buffer[i + 1] = ALARM_COMPONENT_DISABLED - frequency_name = value[1] - error_message = "%s is not a supported frequency" % frequency_name - if frequency_name not in FREQUENCY: - raise ValueError(error_message) - - frequency = FREQUENCY.index(frequency_name) - if frequency < 1 and not self.has_seconds: - raise ValueError(error_message) - - # i is the index of the minute byte - i = 2 if self.has_seconds else 1 - - if frequency > 0 and self.has_seconds: # minutely at least - self.buffer[1] = _bin2bcd(value[0].tm_sec) - - if frequency > 1: # hourly at least - self.buffer[i] = _bin2bcd(value[0].tm_min) - - if frequency > 2: # daily at least - self.buffer[i + 1] = _bin2bcd(value[0].tm_hour) - - if value[1] == "weekly": - if self.weekday_shared: - self.buffer[i + 2] = ( - _bin2bcd(value[0].tm_wday + self.weekday_start) | 0x40 - ) - else: - self.buffer[i + 3] = _bin2bcd(value[0].tm_wday + self.weekday_start) - elif value[1] == "monthly": - self.buffer[i + 2] = _bin2bcd(value[0].tm_mday) - - with obj.i2c_device: - obj.i2c_device.write(self.buffer) diff --git a/src/lib/adafruit_register/i2c_bcd_datetime.mpy b/src/lib/adafruit_register/i2c_bcd_datetime.mpy deleted file mode 100644 index 4ca5d752bf25244f7b933f3fd4797de2f412ae50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1141 zcmYLG+j81g6g|Q>h%pF6R9~p=0H!v@$ic2B?Tgz$-Nw^SgKr&XnmG~D0hAPpkq&Wq zLc)Z&?L&V>Nj@PT(oe|L&h*vY=TPX3Mp|oWU)L_pqBSLOr0Qzj?idu?q+vLe*oslq za81*(u2Mpc7Ads5C>p+B__A>S0SYn&i9fZeVVUZ$D0td!6BMT1wqZ6<Z?4!`7g4yGxsviANa)orhqZ$2chauv9L?Ag9pw z_`eB}$mT2743i-RyArvi-xkFVOXiIgTPj*8Qj2nbv zj1p|^QFD~Nuwf5Oo5TcLRNXMCg(6mmVq)q@G)z3IB|FsaXcTi#pc1r$N@3apr15VH zW{d!DS{<8%3z&?976UA_WsO3*M72@>k&H2z!z_bRB8L8L3($}KZfz@`_&EGp~w?vt=cn~w)PAhC1~cLNom{pN>LgHZFXt}&1xy*qbuKxuU{J7;_>Jf zW2Xy{gJy%bz23pW+}bS~>iLxL^lLYiDIcba`7>=090bLHoJu1(?N(N%?!JGQxC_bf zh_WYs;_VLt)ygkV;!}ymco@{zXTB7FefD8bNd5WvnoC0_4VlF0D_;_Cm^7G3myBLc zXrIykgnEp66FPwWYbL!q``}Bln?0e|%Z2s@?ds*h>eANshTGW5=TpZ!`JHTh;jOd` zqqsc?je6&duYdzmw9j#p<4cY|g<{v7^A22F<(yB<`5Na#j@My9XwG&%XVxrK;hne# Ld|zOeRJ4Bq)KgmQ diff --git a/src/lib/adafruit_register/i2c_bcd_datetime.py b/src/lib/adafruit_register/i2c_bcd_datetime.py deleted file mode 100644 index 215f274..0000000 --- a/src/lib/adafruit_register/i2c_bcd_datetime.py +++ /dev/null @@ -1,122 +0,0 @@ -# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT -# pylint: disable=too-few-public-methods - -""" -`adafruit_register.i2c_bcd_datetime` -==================================================== - -Binary Coded Decimal date and time register - -* Author(s): Scott Shawcroft -""" - -<<<<<<< HEAD -__version__ = "1.9.18" -======= -<<<<<<< HEAD -__version__ = "1.10.1" -======= -__version__ = "1.9.18" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" - -import time - -try: - from typing import Optional, Type - from typing_extensions import Literal - from circuitpython_typing.device_drivers import I2CDeviceDriver -except ImportError: - pass - - -def _bcd2bin(value: int) -> int: - """Convert binary coded decimal to Binary - - :param value: the BCD value to convert to binary (required, no default) - """ - return value - 6 * (value >> 4) - - -def _bin2bcd(value: int) -> int: - """Convert a binary value to binary coded decimal. - - :param value: the binary value to convert to BCD. (required, no default) - """ - return value + 6 * (value // 10) - - -class BCDDateTimeRegister: - """ - Date and time register using binary coded decimal structure. - - The byte order of the register must* be: second, minute, hour, weekday, day (1-31), month, year - (in years after 2000). - - * Setting weekday_first=False will flip the weekday/day order so that day comes first. - - Values are `time.struct_time` - - :param int register_address: The register address to start the read - :param bool weekday_first: True if weekday is in a lower register than the day of the month - (1-31) - :param int weekday_start: 0 or 1 depending on the RTC's representation of the first day of the - week - """ - - def __init__( - self, - register_address: int, - weekday_first: bool = True, - weekday_start: Literal[0, 1] = 1, - ) -> None: - self.buffer = bytearray(8) - self.buffer[0] = register_address - if weekday_first: - self.weekday_offset = 0 - else: - self.weekday_offset = 1 - self.weekday_start = weekday_start - # Masking value list n/a sec min hr day wkday mon year - self.mask_datetime = b"\xFF\x7F\x7F\x3F\x3F\x07\x1F\xFF" - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> time.struct_time: - # Read and return the date and time. - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - return time.struct_time( - ( - _bcd2bin(self.buffer[7] & self.mask_datetime[7]) + 2000, - _bcd2bin(self.buffer[6] & self.mask_datetime[6]), - _bcd2bin(self.buffer[5 - self.weekday_offset] & self.mask_datetime[4]), - _bcd2bin(self.buffer[3] & self.mask_datetime[3]), - _bcd2bin(self.buffer[2] & self.mask_datetime[2]), - _bcd2bin(self.buffer[1] & self.mask_datetime[1]), - _bcd2bin( - (self.buffer[4 + self.weekday_offset] & self.mask_datetime[5]) - - self.weekday_start - ), - -1, - -1, - ) - ) - - def __set__(self, obj: I2CDeviceDriver, value: time.struct_time) -> None: - self.buffer[1] = _bin2bcd(value.tm_sec) & 0x7F # format conversions - self.buffer[2] = _bin2bcd(value.tm_min) - self.buffer[3] = _bin2bcd(value.tm_hour) - self.buffer[4 + self.weekday_offset] = _bin2bcd( - value.tm_wday + self.weekday_start - ) - self.buffer[5 - self.weekday_offset] = _bin2bcd(value.tm_mday) - self.buffer[6] = _bin2bcd(value.tm_mon) - self.buffer[7] = _bin2bcd(value.tm_year - 2000) - with obj.i2c_device: - obj.i2c_device.write(self.buffer) diff --git a/src/lib/adafruit_register/i2c_bit.mpy b/src/lib/adafruit_register/i2c_bit.mpy deleted file mode 100644 index 53762b9ba6e99bd7564a87edd81f375bbdd7644f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 801 zcmZvY(M}pc6ozL(5LSc{aEh{3E0#9IvK6VpUbHH-RgzXg)Dml^u)DxmWy$WKA!(Cc zstLXgN^g4IH|Ptr=?mD{r0Hb`s7)`LOmfbgobNy9{7E~Qyu&35a-pQv3B{UHCOTC# zk;EiiBs5xU0FP@fx)_~H0JvVGM6DJMfFswaDFD7uvkI+iRp6qH8mX4S#9AzwQjUnE zq%?A*XyBHk<8nmKTalb7p_gb*r>(K$9-B}@&h4pR-K{gj37W6m3RjijvOvO=b zPzAU#E-Msc;O{Qi*8t&TjZg*Cic)3E7i3bUDsZcHij}GiAgN-V7Bt$0^&Z?@Lm0Ew zban~Tf|y}7m1WPxdk>nvscQ#tL6$W|*MXg_1BAcjVcyy}9*Wl{i_6z;C;R4X^v%is@jl8JaQb$3oO``w zIA>DRi7n|&gcgKC0Nq#(8_)^|pepPs=$nhQ^^214vN;dXP$t4{ z&U45Uvmk#)vYvpCC*1guaC2gE%9yCp$)N%F8vR^NNx1NmtFGf?)J+1KmX6)cDFnJQC3;t ix>GWeISY8cwQBIa+j$g>f$phXCFtsn+w_LxK=}>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" - -try: - from typing import Optional, Type, NoReturn - from circuitpython_typing.device_drivers import I2CDeviceDriver -except ImportError: - pass - - -class RWBit: - """ - Single bit register that is readable and writeable. - - Values are `bool` - - :param int register_address: The register address to read the bit from - :param int bit: The bit index within the byte at ``register_address`` - :param int register_width: The number of bytes in the register. Defaults to 1. - :param bool lsb_first: Is the first byte we read from I2C the LSB? Defaults to true - - """ - - def __init__( - self, - register_address: int, - bit: int, - register_width: int = 1, - lsb_first: bool = True, - ) -> None: - self.bit_mask = 1 << (bit % 8) # the bitmask *within* the byte! - self.buffer = bytearray(1 + register_width) - self.buffer[0] = register_address - if lsb_first: - self.byte = bit // 8 + 1 # the byte number within the buffer - else: - self.byte = register_width - (bit // 8) # the byte number within the buffer - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> bool: - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - return bool(self.buffer[self.byte] & self.bit_mask) - - def __set__(self, obj: I2CDeviceDriver, value: bool) -> None: - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - if value: - self.buffer[self.byte] |= self.bit_mask - else: - self.buffer[self.byte] &= ~self.bit_mask - i2c.write(self.buffer) - - -class ROBit(RWBit): - """Single bit register that is read only. Subclass of `RWBit`. - - Values are `bool` - - :param int register_address: The register address to read the bit from - :param type bit: The bit index within the byte at ``register_address`` - :param int register_width: The number of bytes in the register. Defaults to 1. - - """ - - def __set__(self, obj: I2CDeviceDriver, value: bool) -> NoReturn: - raise AttributeError() diff --git a/src/lib/adafruit_register/i2c_bits.mpy b/src/lib/adafruit_register/i2c_bits.mpy deleted file mode 100644 index a9846d634f5635478949327a402bf6a65ec0c381..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1053 zcmZvZ-%}G;6vyu-1Weo{H=DSP5-|pnV1%TEtz&(ukSGXM2_g(LI=7qMOLi^nrn@&V z>2${3Oc`STnnePCfJXm-_R)uS+D_j(#^N8)n?-zL=FXgZ@A>iloO4Qp;L3#OmMDuG zMnfgYz%|t**eIw)2~|~M=Ic!mW~cIB=cjH0_^?h?T@yFKxs_%e1NcB+#-w3rfF;ek zs@1^Ay<(}1w^a$34RsqE;I5<^60KZs5=Gb0NmyQXLy+von1Eego26RRK6LFUq|#_h zG`|P2O??NOq|XA_YGY%A*5o$LD%wyDlK^O{HO+N~5UOE<5Qz2BPXs__$507IM8O(u zO_WuQ=pdvw2*R2SpsFF0hz0>P*>saz_jrUTw@KTiwIHHtb)DYs7bd@IMWNQ%a>oLa zr;|dWEF0J~LE`hzjw%xcuzfTv1Knl@_3AS!TYW~`$G~pdYtX=b73yx?nelz25K=d9 z6$&+#D2-}f(zgnyD=EyJ?xu7y$)%G?E`Qoop5_4GcuCYWog@`;8z;AP11IUg%p_4n z?K5f8RDZ-^_osXCy?{EFIwSW zqU~5@m`^}If7xDM%6T761O9v^$2^*5_(;*i^A*XvJOA4*lR7_gB^kULPFYh6*Rx}} z%$U93vyTsYPMme3tlepU?7b_`aigP*z~BQX9^}UC&VK^q#6vHgXvkVUbmHNo@|?YI zANOWwEGG^t(%$@DBo!G+XRONqJJJhf503LA>72Ec$xKu2$@I6`^0i!s(gy!})o0i_i{Wg5`&`!kge7>+ z=J@r`Zs+A6U6&LsM&5Q(_H)V>I$G_t|L)FC9>l(&uNzFi%Zg-AfIy%vTdcdD0Drj% R`paOHVb+EIfx9jM{6D?nP#gdN diff --git a/src/lib/adafruit_register/i2c_bits.py b/src/lib/adafruit_register/i2c_bits.py deleted file mode 100644 index 8a85396..0000000 --- a/src/lib/adafruit_register/i2c_bits.py +++ /dev/null @@ -1,122 +0,0 @@ -# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT -# pylint: disable=too-few-public-methods - -""" -`adafruit_register.i2c_bits` -==================================================== - -Multi bit registers - -* Author(s): Scott Shawcroft -""" - -<<<<<<< HEAD -__version__ = "1.9.18" -======= -<<<<<<< HEAD -__version__ = "1.10.1" -======= -__version__ = "1.9.18" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" - -try: - from typing import Optional, Type, NoReturn - from circuitpython_typing.device_drivers import I2CDeviceDriver -except ImportError: - pass - - -class RWBits: - """ - Multibit register (less than a full byte) that is readable and writeable. - This must be within a byte register. - - Values are `int` between 0 and 2 ** ``num_bits`` - 1. - - :param int num_bits: The number of bits in the field. - :param int register_address: The register address to read the bit from - :param int lowest_bit: The lowest bits index within the byte at ``register_address`` - :param int register_width: The number of bytes in the register. Defaults to 1. - :param bool lsb_first: Is the first byte we read from I2C the LSB? Defaults to true - :param bool signed: If True, the value is a "two's complement" signed value. - If False, it is unsigned. - """ - - def __init__( # pylint: disable=too-many-arguments - self, - num_bits: int, - register_address: int, - lowest_bit: int, - register_width: int = 1, - lsb_first: bool = True, - signed: bool = False, - ) -> None: - self.bit_mask = ((1 << num_bits) - 1) << lowest_bit - # print("bitmask: ",hex(self.bit_mask)) - if self.bit_mask >= 1 << (register_width * 8): - raise ValueError("Cannot have more bits than register size") - self.lowest_bit = lowest_bit - self.buffer = bytearray(1 + register_width) - self.buffer[0] = register_address - self.lsb_first = lsb_first - self.sign_bit = (1 << (num_bits - 1)) if signed else 0 - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> int: - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - # read the number of bytes into a single variable - reg = 0 - order = range(len(self.buffer) - 1, 0, -1) - if not self.lsb_first: - order = reversed(order) - for i in order: - reg = (reg << 8) | self.buffer[i] - reg = (reg & self.bit_mask) >> self.lowest_bit - # If the value is signed and negative, convert it - if reg & self.sign_bit: - reg -= 2 * self.sign_bit - return reg - - def __set__(self, obj: I2CDeviceDriver, value: int) -> None: - value <<= self.lowest_bit # shift the value over to the right spot - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - reg = 0 - order = range(len(self.buffer) - 1, 0, -1) - if not self.lsb_first: - order = range(1, len(self.buffer)) - for i in order: - reg = (reg << 8) | self.buffer[i] - # print("old reg: ", hex(reg)) - reg &= ~self.bit_mask # mask off the bits we're about to change - reg |= value # then or in our new value - # print("new reg: ", hex(reg)) - for i in reversed(order): - self.buffer[i] = reg & 0xFF - reg >>= 8 - i2c.write(self.buffer) - - -class ROBits(RWBits): - """ - Multibit register (less than a full byte) that is read-only. This must be - within a byte register. - - Values are `int` between 0 and 2 ** ``num_bits`` - 1. - - :param int num_bits: The number of bits in the field. - :param int register_address: The register address to read the bit from - :param type lowest_bit: The lowest bits index within the byte at ``register_address`` - :param int register_width: The number of bytes in the register. Defaults to 1. - """ - - def __set__(self, obj: I2CDeviceDriver, value: int) -> NoReturn: - raise AttributeError() diff --git a/src/lib/adafruit_register/i2c_struct.mpy b/src/lib/adafruit_register/i2c_struct.mpy deleted file mode 100644 index 21b38000c7d98d73c16da70e03ab7958c0c1f144..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1040 zcmZvZ-A)=o7=~v-5OKlb$0-U`E4o&Tpj%2?o3t9ys)@BIYDhHOWf@?t>~3}k4L#bW zCdIo!=^fbn=p{7iNl!a0k~EFU9L&%Az2En|v+f3y3FfU>63cpBCW4MDvO%z(l6grm zh+daSQfq(_b~*Vvx%>*Sy*mK+G$N~tcn&=IhK7MRU)RntaIYy106(bvnACLzu%w~M zN(D@8^XplBAxk)`%NJM&o06RaFssvSi7Y2qjx%0QSJ{5740^>W%xu`2OC% z$G9~QCGlJ`B`B6D*J_Rw$)dk`|ajV5MIz#b{27p1hhHI+cxRCKBXl|^z-cK-_mceBOLG7I|B|!@d?Sfz=F_SM(;y1Laja3nr6YjQLEZ94*koE3D21MLvZFu&7GI}*HUhl1u&PWoE?#lTQ} z?x~sLGB=^{`_a2jc$7ZU1(5|GS(z(GA$M|0>m#&2I8PtajFn3|4l@p^sU5?y_}qe- zUgD@?oGR$Mb=!J5Q0Tf1Q@1F+$uM1yoYV#IwN@y=RBPA<@I44%MdE%NU}L5`|4lm* z%Sl&Tf0(d6>+TZA@&7^HwL`IvdDQJ1`0+hF+UYzrf5*x=%|q$Ra~!tk&M$F`+`~@y pJRR#c(}NkJw$V->#YzCLzjb1IZ4AGSL9=J7QPy^@1fQh={tHqnAt(R< diff --git a/src/lib/adafruit_register/i2c_struct.py b/src/lib/adafruit_register/i2c_struct.py deleted file mode 100644 index fc4762c..0000000 --- a/src/lib/adafruit_register/i2c_struct.py +++ /dev/null @@ -1,112 +0,0 @@ -# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT -# pylint: disable=too-few-public-methods - -""" -`adafruit_register.i2c_struct` -==================================================== - -Generic structured registers based on `struct` - -* Author(s): Scott Shawcroft -""" - -<<<<<<< HEAD -__version__ = "1.9.18" -======= -<<<<<<< HEAD -__version__ = "1.10.1" -======= -__version__ = "1.9.18" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" - -import struct - -try: - from typing import Optional, Type, Tuple, Any, NoReturn - from circuitpython_typing.device_drivers import I2CDeviceDriver -except ImportError: - pass - - -class Struct: - """ - Arbitrary structure register that is readable and writeable. - - Values are tuples that map to the values in the defined struct. See struct - module documentation for struct format string and its possible value types. - - :param int register_address: The register address to read the bit from - :param str struct_format: The struct format string for this register. - """ - - def __init__(self, register_address: int, struct_format: str) -> None: - self.format = struct_format - self.buffer = bytearray(1 + struct.calcsize(self.format)) - self.buffer[0] = register_address - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> Tuple: - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - return struct.unpack_from(self.format, memoryview(self.buffer)[1:]) - - def __set__(self, obj: I2CDeviceDriver, value: Tuple) -> None: - struct.pack_into(self.format, self.buffer, 1, *value) - with obj.i2c_device as i2c: - i2c.write(self.buffer) - - -class UnaryStruct: - """ - Arbitrary single value structure register that is readable and writeable. - - Values map to the first value in the defined struct. See struct - module documentation for struct format string and its possible value types. - - :param int register_address: The register address to read the bit from - :param str struct_format: The struct format string for this register. - """ - - def __init__(self, register_address: int, struct_format: str) -> None: - self.format = struct_format - self.address = register_address - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> Any: - buf = bytearray(1 + struct.calcsize(self.format)) - buf[0] = self.address - with obj.i2c_device as i2c: - i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) - return struct.unpack_from(self.format, buf, 1)[0] - - def __set__(self, obj: I2CDeviceDriver, value: Any) -> None: - buf = bytearray(1 + struct.calcsize(self.format)) - buf[0] = self.address - struct.pack_into(self.format, buf, 1, value) - with obj.i2c_device as i2c: - i2c.write(buf) - - -class ROUnaryStruct(UnaryStruct): - """ - Arbitrary single value structure register that is read-only. - - Values map to the first value in the defined struct. See struct - module documentation for struct format string and its possible value types. - - :param int register_address: The register address to read the bit from - :param type struct_format: The struct format string for this register. - """ - - def __set__(self, obj: I2CDeviceDriver, value: Any) -> NoReturn: - raise AttributeError() diff --git a/src/lib/adafruit_register/i2c_struct_array.mpy b/src/lib/adafruit_register/i2c_struct_array.mpy deleted file mode 100644 index 8871367e98a10a4d912ad99c58c24a991646b91a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1008 zcmYk3ZEw?76vwagM4p=5*yb)Yp)E~F(*Vh0veF`?jiwZ+HVs3e%C_p(y>?ndYg_gW zD0`6XP)*&paS}cO-8a~aNxa#2Kzsmh;#T90b*%Hh=YRgc(~g1pW@t-Mm9E{>DYmr( z-JzP@&?SN$YWE1mift?Xy445SbhExzZ?1xL&;iNqo^_-F_@hO2(@>5;e7kRHAWi$0 zZXAHQ$5MMkd#w{~!`5GGHrOP(O_+_|B)HZ{LL@doJ2 zpOUv+?CQ4j|FR%v?!N@yWrPnjiuZfnF7tv!IUH>V zb%UB9WA-T43>84#V5t?G0@O1sg}lOD+dKwq{Is5G2alICimKY0;{Z5CgLM^T@YEXv zgfUC%FdP^Ih?$3Fvg(J%t#1ax!UbM@F=6(cao4nu6$-pH+2aNeGAs{+qXF;spx`y4 z-yTwGId>b41DziB_UptvZd{zKasT3W?EvnR0PfTCOV-&N5Y3z)bGq;PJiIen6O5IcUawSxTNpxKT zC@pa)Cxwt8h0zr$f{HSU4!|&i(v|E|6}mUdI`%0_Lw& zmT$W2OZ95-@{ILoS%2(}PVYoI!JNe3@5I7*g`2#W$&*C9SaBb*6^ZwIJd-;8-i7BN zQUM6L#yz{uq4^20svi;4l}Y|xmY-#xVcD-T4zR?DGi+BsKPR&Y|8U}bC40+#Rcn`I hzCQGYxmKvX!#5@spJd;wY%ONminPKV{_8+MJ_8bIDg^)l diff --git a/src/lib/adafruit_register/i2c_struct_array.py b/src/lib/adafruit_register/i2c_struct_array.py deleted file mode 100644 index 0dfee57..0000000 --- a/src/lib/adafruit_register/i2c_struct_array.py +++ /dev/null @@ -1,122 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT -# pylint: disable=too-few-public-methods - -""" -`adafruit_register.i2c_struct_array` -==================================================== - -Array of structured registers based on `struct` - -* Author(s): Scott Shawcroft -""" - -<<<<<<< HEAD -__version__ = "1.9.18" -======= -<<<<<<< HEAD -__version__ = "1.10.1" -======= -__version__ = "1.9.18" ->>>>>>> ae84eef1491903d49de0e32510d1ab243185d8ff ->>>>>>> origin/update_dependencies -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" - -import struct - -try: - from typing import Tuple, Optional, Type - from circuitpython_typing.device_drivers import I2CDeviceDriver -except ImportError: - pass - - -class _BoundStructArray: - """ - Array object that `StructArray` constructs on demand. - - :param object obj: The device object to bind to. It must have a `i2c_device` attribute - :param int register_address: The register address to read the bit from - :param str struct_format: The struct format string for each register element - :param int count: Number of elements in the array - """ - - def __init__( - self, - obj: I2CDeviceDriver, - register_address: int, - struct_format: str, - count: int, - ) -> None: - self.format = struct_format - self.first_register = register_address - self.obj = obj - self.count = count - - def _get_buffer(self, index: int) -> bytearray: - """Shared bounds checking and buffer creation.""" - if not 0 <= index < self.count: - raise IndexError() - size = struct.calcsize(self.format) - # We create the buffer every time instead of keeping the buffer (which is 32 bytes at least) - # around forever. - buf = bytearray(size + 1) - buf[0] = self.first_register + size * index - return buf - - def __getitem__(self, index: int) -> Tuple: - buf = self._get_buffer(index) - with self.obj.i2c_device as i2c: - i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) - return struct.unpack_from(self.format, buf, 1) # offset=1 - - def __setitem__(self, index: int, value: Tuple) -> None: - buf = self._get_buffer(index) - struct.pack_into(self.format, buf, 1, *value) - with self.obj.i2c_device as i2c: - i2c.write(buf) - - def __len__(self) -> int: - return self.count - - -class StructArray: - """ - Repeated array of structured registers that are readable and writeable. - - Based on the index, values are offset by the size of the structure. - - Values are tuples that map to the values in the defined struct. See struct - module documentation for struct format string and its possible value types. - - .. note:: This assumes the device addresses correspond to 8-bit bytes. This is not suitable for - devices with registers of other widths such as 16-bit. - - :param int register_address: The register address to begin reading the array from - :param str struct_format: The struct format string for this register. - :param int count: Number of elements in the array - """ - - def __init__(self, register_address: int, struct_format: str, count: int) -> None: - self.format = struct_format - self.address = register_address - self.count = count - self.array_id = "_structarray{}".format(register_address) - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> _BoundStructArray: - # We actually can't handle the indexing ourself due to data descriptor limits. So, we return - # an object that can instead. This object is bound to the object passed in here by its - # initializer and then cached on the object itself. That way its lifetime is tied to the - # lifetime of the object itself. - if not hasattr(obj, self.array_id): - setattr( - obj, - self.array_id, - _BoundStructArray(obj, self.address, self.format, self.count), - ) - return getattr(obj, self.array_id) diff --git a/src/lib/adafruit_requests.mpy b/src/lib/adafruit_requests.mpy deleted file mode 100644 index bf5ab312bb2fe3a33c37e9e1d7c5203ac3a023cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5767 zcmZuzU2qdumcA`pvan^jEw$SMY}2S)KmL(ygir{9#FpAd{sCK-444FDxz)B6WJ%Ev z#$id+io*_*-7P=t%ho<5HM?6i`___d1A&=YFl0#}GgZ3}#50p*_kld^6H~P{wNHC) zOLFkcKvnzRd(J)g{Cwv-@@dc^sjtf(iAEOExmY%w7T?Q>nQX?HT1735N1ZOG3pI-A zbRvng?`Dz-q{*yiP~W1M4GYOcLKL#GWFovANkkUKH1h1)`3u61*m5c^E{ln51Ybhh zL}Xb+2SVwHAkIgGE1}hth#Ir-%^=NCVijrmn2<%}WD2WC;z+A#=s+WuSVZ<0r0z#JgFQk*pH8<&Sb}5})2}f2UtEj;067#EB zNFu$E7Iz(y!i6LHS_D)^%sPZ*@=8oZ^?eND!dD~lSTvFq!--@zy!d`Bh4j1LDO)LE zie<$#j#$Y%rPvB*V(*Jciwlml@nj?lRn27L6)iZUYcYjmQ3yL8$zZ@&tJnb<6@@Fi zX^_rH-HmV|5k-YI-SE)qq0zHQGdwy8H}PpWk_lHLgGcbb;vqUv*;N|}z10}aEvGW5 zj=X-udi}o<^gDVYHW{rF4eGGf{R%>&?}X7GAQS?g=}n+tXOG#1wYYFx@@QzwuP z|H+&{suPI9U0AIwi-pholJ@8 zEC8pwU?g{oqg5)afkr@D95K?@&?yn3bV^c_;xz$xw1^CN1pvyyGF#&oRB%Y=sbz6F znO?mb6IW27KPma7S*g7vD&lSk=MpicE5dXoqhU-eXsB>X3qXvZh^|`R!mw^x1pa{h zK=4aQlS{{;?X%EK%cv_7j|1}4Vl)O#lFcCLXp{P_O2zv77h~C_+`JPq(qH4r{-GN2 z`YPNsRpF-T8V$o8q;B3rTu4JONZmP;h|I@DCYxk1#W4O*XbM&pFguY()!c~GC(ep1 zVw#z*9y1CVM49YTEW;!dqLTr{?MgFDYi`%!4!8v7wNCNtsnHQOeAOL*DC=sHF!h*)WO_LQ zr9b6Y<^uy1on`FTZ*()5YMJXdc5fsX7>s)cAczA~veBo!15~3XBdJt8R%!fxC1~~G zSC6haR#sLV_%#OrSt%#@rBN~cNYkW%s{e@d2*wg!gDSHJsz|M?M(I%6Pp$J+IN1@x z-Kf^rwiA`9rlw~Sy9vN0%_fCp9AKs){77w@7E>YsFv?WAkXeYt;yHjd`s5_zB<2S5 zhdJJA8=2_qyL@eqxn+zq|DYvR^am`WKgD%k#3yLlRC1Q)RQee^#{og9j)tVrkgwTw7#Q-ZUJd4X&{X~ zbqA@AR=TyrKpQ%Yv~f1%LS&m;MYn}q1WC6_gu9NWLoOBB4u|a_R~>1Bg()D^7y7ii zwol%O{$yXPq@txdvW=GNU5MWQVGj{JO3(*gD*8}BsJnJRzh7#QwE2@o*>oTv6quiZ z5Ki{x-naM+-)R^}V!ZF1tha}R_pPa3}N5gvo z;g(Lzw|_D{$N1LvN7uAVYn``@YxcEf_}{+PbW3}y>6U)YP`%QQuh3@frHi1CfQPO; z_O_08yXxugK%hqzJgTN&b7^SC)j%J2Y3ZY`eYDfnNDsA2YFCq+puKpZgGC2kG{fSw zOHbEJQ}}KRJsc3oTUy_79CMST%g=HaPGq=lj<>VDb~n?*SUE^UCwW*lnFF$k?L)GO z%gZKOHV>$duEAX2DW_Nj41x1pmIqBT_>?Eeq1X&+BOyc-JZ!BxC-4nL3I!;k-;3 z+&JY>nH%sHb*xzOsDz)U(#N~un9Aolj_Dq@jdFe5Jk#UsGxJ9*oJ}@bAIe6{c8T&7 z^JP!nqmBIU_=Qqn!>O8iykU^qif-cN7v-%#{CjDu3}>alZKvwoZ3uW%D1*4_GF*Lx{jCQ*MJqgKjwhHax_ack?^^I8IwBfUOGUyDzrA|Mx*a zxS4vICwgAPft<*HYBO^db2r;(vkr`K;3nMi<^gEnZ}~azEp3BUh`>!eD)JK>im4+0 zo-2dSbN(IpmjCd01MlD!&Jt@k-~#7`GJwT94t4_H@m?>)R>}AyIN`@P;QI~F!}pO)y;j`ts2*>E z0Y79@*nO%8$Kcn_#ZA=~4Xv#=r3$kzQQ5_}s_M&KApm{BvnQ_-J51eH5_xg0Xmq zC;d1#-T;(jfS-g90R4>$(7jkx0Vnhebo#ZS9w*#;Ima+|mObKkbX&Wv{f@~h>K_z+ zKJOp%&=i0;NM8e_Z$R~MyTt!jzRdp~1nnjAuvixPxpZ zcHa&^S*A{T5Y{8#+<`_ck#Ftr=Ss4X`xg7il`hPXgXNbm-ah9Y0c3Eca|-eb{#I&8 zYFOU~{*AM2qipnk%Ln1hHqMYfNV`EYu|OeWKv>$R_kw2I90S*dHRcH20ozW-?%@0` z*~kXI`%fM^eLpNA9=8hDkcHCfSzsQKZKimc8YV|RgRBd1L}~~i1euVH9kS8vR#gq%w0E29!O*cmi;J^c{3E@9XRyX!u6cC8ARX*c zy=t#T0d_y2yiLZ5?5`O|6qK>wLjZVK9}mpORtTpk++?2q8(|-o8b+j!_0xl_-^SQ1 zW=qg!DdALgvLx69UVv(D%M?@g5I`-^zTW6Y@i`xayg=_l)fA?26OX;8;RyDRHy3BU z`WmhMb=-c!^p6(@VGihS(a|;C+M9ouh%!I^uZ{*29t{fr!*FPoG%n~tdIB$0u$Xk! z(LopyAr}n#Kp-$>J~`c^2_DtZmtjsMVNSdUb0PzCBIjzNS6n)J)zwU24+#3gz|YqR z6P^ZLvBmXyJI5I`5Rq)^0a_b{?i~Y?9uEi~`(yNkcRkkY?>lB@tvJ(=WVZ_Q#v!&< zDc2bf3Xk^>s2%`(_m`k>5HK!-Z@8p%KVan$YoN}SiUx@R!tmfZ0gYq&4;44rDIH$V znOXCNF+P3NVjJ$sKlJ`j-VceIDwZmwx;=L4yg@R5&(A&@ZP_r^8S0FI9iAV2oL5By zkL>lA#yUZ+|DKq8hf zejgc>8n9PCm8nVam{htGCeFvqoqBT!%UFq9|67aM-rYN9$Hp;FP(eJFX7L@i0Tz%7 z7$dkx#_(Agj(|Bp3$GNddRPR6zpt0q>))9Hvlk_cWc5mi>(~Ejao%cYV@{T}oLzK| z+Pm$s1#6GBx4Wl1WO2j*suP>Q%Yi4iaYKf-0^j+3hI1lod}5Fgb^f-8_+pE{0v>EB zwc_Vk>Ir$_%dPNu%U9$qb@uM(Tc6K(>b~^$-<5ERK|55@#?1(rki+5UcC3M55f!;$+t_Mx>@oa*pDmwOKdwF+~KEKQ%#56e+Q1X_=~I- zjzU~L z93F3cP{b73PkY_MzZ{o-_A&9#s>^_Li)4e`dL{dk)V==Sb91cEd^Y5;!oQI*Yu{;$ zJ<6EPEcY(s_p|1=S%=s=ZZ`+n)8?L0&fm=)@{W#KM|0!m-t&`|vzAStuMa);A1G0; zli`nm(d)nb>glul)EpTh=RK-tV+R3}rtkUStfEl>jXKaEFFYF~F5M*s_;G2@Pkrq@ zcbAOf^+#`f?FC$#*n@XpLU)kjJ>ckjU|dwj#nZ206#T+Xd@UQt`3d;c2GDr^*MZ-7 zKb3wdz4)~ZLu%pPFYc0yvT=-Dx=qGp2f0@MS1_1*IFjrFM2vGJ)g^fdtI<0L%qO{Ftv$|#~UiYU8Rlv6|jMRawqXhjjtD57h7MXQQvRuR3wS9DzwT~b7URTKR`*6s{! diff --git a/src/lib/adafruit_ticks.mpy b/src/lib/adafruit_ticks.mpy deleted file mode 100644 index 6167b2f161ca616c28220d7ab71cab204e04c254..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 694 zcmYLEQE$>v7`-j)CF)J23Yq_nhy1=iKkxTLpJgv{hXpX6Wuyfr3 zSaW&znA^#b1E5#c+BS&U{jSkFF>R{{SjTJ|AknrumSc5HUF+CDSTZ2^rdquYVm_>! zjm84E4BG}s(@u<@ZCV{o15nd?M%NM|bxQEkb z$02LiT4{?6+-O-J!ME)n^e**2jbr8QQBst$r59fXvHbLuzgIpJM!^w_A)TR;ffS8s z6|SILTL4A(qZl%zx|(7TqaGYp`86J9=*!|ytl~;Ch=M%N2O(2Y0#zgwOEN%AVXz;^ zkR`5Qibd-xzpemPrVw+G_UwEt6SyqCQZWp9Jt4HA$~2LwcjU?e#K|itEHfxl(O2CB z%Q5&djtYVTM0Ylueg37p`FZg*DUxXM`zZfssl0(iF^PhN4WgYke=F2N{Bl=>Ts(n4 zr5|L)8O9h)s)y53NOb3TS}43#i_gZBD{(mIe|6#Z7Nt*IM-cPj6_KPSzZW_Ckl= thresh: - self.callback(cmd, addr, ext, *self.args) - else: - self._errf(cmd) - - def error_function(self, func): - self._errf = func - - def close(self): - self._pin.irq(handler = None) - self.tim.deinit() diff --git a/src/lib/ir_rx/acquire.py b/src/lib/ir_rx/acquire.py deleted file mode 100644 index af3ab38..0000000 --- a/src/lib/ir_rx/acquire.py +++ /dev/null @@ -1,108 +0,0 @@ -# acquire.py Acquire a pulse train from an IR remote -# Supports NEC protocol. -# For a remote using NEC see https://www.adafruit.com/products/389 - -# Author: Peter Hinch -# Copyright Peter Hinch 2020 Released under the MIT license - -from machine import Pin, freq -from sys import platform - -from utime import sleep_ms, ticks_us, ticks_diff -from ir_rx import IR_RX - - -class IR_GET(IR_RX): - def __init__(self, pin, nedges=100, twait=100, display=True): - self.display = display - super().__init__(pin, nedges, twait, lambda *_ : None) - self.data = None - - def decode(self, _): - def near(v, target): - return target * 0.8 < v < target * 1.2 - lb = self.edge - 1 # Possible length of burst - if lb < 3: - return # Noise - burst = [] - for x in range(lb): - dt = ticks_diff(self._times[x + 1], self._times[x]) - if x > 0 and dt > 10000: # Reached gap between repeats - break - burst.append(dt) - lb = len(burst) # Actual length - # Duration of pulse train 24892 for RC-5 22205 for RC-6 - duration = ticks_diff(self._times[lb - 1], self._times[0]) - - if self.display: - for x, e in enumerate(burst): - print('{:03d} {:5d}'.format(x, e)) - print() - # Attempt to determine protocol - ok = False # Protocol not yet found - if near(burst[0], 9000) and lb == 67: - print('NEC') - ok = True - - if not ok and near(burst[0], 2400) and near(burst[1], 600): # Maybe Sony - try: - nbits = {25:12, 31:15, 41:20}[lb] - except KeyError: - pass - else: - ok = True - print('Sony {}bit'.format(nbits)) - - if not ok and near(burst[0], 889): # Maybe RC-5 - if near(duration, 24892) and near(max(burst), 1778): - print('Philps RC-5') - ok = True - - if not ok and near(burst[0], 2666) and near(burst[1], 889): # RC-6? - if near(duration, 22205) and near(burst[1], 889) and near(burst[2], 444): - print('Philips RC-6 mode 0') - ok = True - - if not ok and near(burst[0], 2000) and near(burst[1], 1000): - if near(duration, 19000): - print('Microsoft MCE edition protocol.') - # Constant duration, variable burst length, presumably bi-phase - print('Protocol start {} {} Burst length {} duration {}'.format(burst[0], burst[1], lb, duration)) - ok = True - - if not ok and near(burst[0], 4500) and near(burst[1], 4500) and lb == 67: # Samsung - print('Samsung') - ok = True - - if not ok and near(burst[0], 3500) and near(burst[1], 1680): # Panasonic? - print('Unsupported protocol. Panasonic?') - ok = True - - if not ok: - print('Unknown protocol start {} {} Burst length {} duration {}'.format(burst[0], burst[1], lb, duration)) - - print() - self.data = burst - # Set up for new data burst. Run null callback - self.do_callback(0, 0, 0) - - def acquire(self): - while self.data is None: - sleep_ms(5) - self.close() - return self.data - -def test(): - # Define pin according to platform - if platform == 'pyboard': - pin = Pin('X3', Pin.IN) - elif platform == 'esp8266': - freq(160000000) - pin = Pin(13, Pin.IN) - elif platform == 'esp32' or platform == 'esp32_LoBo': - pin = Pin(23, Pin.IN) - elif platform == 'rp2': - pin = Pin(16, Pin.IN) - irg = IR_GET(pin) - print('Waiting for IR data...') - return irg.acquire() diff --git a/src/lib/ir_rx/mce.py b/src/lib/ir_rx/mce.py deleted file mode 100644 index c3e3849..0000000 --- a/src/lib/ir_rx/mce.py +++ /dev/null @@ -1,68 +0,0 @@ -# mce.py Decoder for IR remote control using synchronous code -# Supports Microsoft MCE edition remote protocol. - -# Author: Peter Hinch -# Copyright Peter Hinch 2020 Released under the MIT license - -# WARNING: This is experimental and subject to change. - -from utime import ticks_us, ticks_diff -from ir_rx import IR_RX - -class MCE(IR_RX): - init_cs = 4 # http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE says 3 - def __init__(self, pin, callback, *args): - # Block lasts ~19ms and has <= 34 edges - super().__init__(pin, 34, 25, callback, *args) - - def decode(self, _): - def check(v): - if self.init_cs == -1: - return True - csum = v >> 12 - cs = self.init_cs - for _ in range(12): - if v & 1: - cs += 1 - v >>= 1 - return cs == csum - - try: - t0 = ticks_diff(self._times[1], self._times[0]) # 2000μs mark - t1 = ticks_diff(self._times[2], self._times[1]) # 1000μs space - if not ((1800 < t0 < 2200) and (800 < t1 < 1200)): - raise RuntimeError(self.BADSTART) - nedges = self.edge # No. of edges detected - if not 14 <= nedges <= 34: - raise RuntimeError(self.OVERRUN if nedges > 28 else self.BADSTART) - # Manchester decode - mask = 1 - bit = 1 - v = 0 - x = 2 - for _ in range(16): - # -1 convert count to index, -1 because we look ahead - if x > nedges - 2: - raise RuntimeError(self.BADBLOCK) - # width is 500/1000 nominal - width = ticks_diff(self._times[x + 1], self._times[x]) - if not 250 < width < 1350: - self.verbose and print('Bad block 3 Width', width, 'x', x) - raise RuntimeError(self.BADBLOCK) - short = int(width < 750) - bit ^= short ^ 1 - v |= mask if bit else 0 - mask <<= 1 - x += 1 + short - - self.verbose and print(bin(v)) - if not check(v): - raise RuntimeError(self.BADDATA) - val = (v >> 6) & 0x3f - addr = v & 0xf # Constant for all buttons on my remote - ctrl = (v >> 4) & 3 - - except RuntimeError as e: - val, addr, ctrl = e.args[0], 0, 0 - # Set up for new data burst and run user callback/error function - self.do_callback(val, addr, ctrl) diff --git a/src/lib/ir_rx/nec.py b/src/lib/ir_rx/nec.py deleted file mode 100644 index 011b13d..0000000 --- a/src/lib/ir_rx/nec.py +++ /dev/null @@ -1,69 +0,0 @@ -# nec.py Decoder for IR remote control using synchronous code -# Supports NEC and Samsung protocols. -# With thanks to J.E. Tannenbaum for information re Samsung protocol - -# For a remote using NEC see https://www.adafruit.com/products/389 - -# Author: Peter Hinch -# Copyright Peter Hinch 2020-2022 Released under the MIT license - -from utime import ticks_us, ticks_diff -from ir_rx import IR_RX - -class NEC_ABC(IR_RX): - def __init__(self, pin, extended, samsung, callback, *args): - # Block lasts <= 80ms (extended mode) and has 68 edges - super().__init__(pin, 68, 80, callback, *args) - self._extended = extended - self._addr = 0 - self._leader = 2500 if samsung else 4000 # 4.5ms for Samsung else 9ms - - def decode(self, _): - try: - if self.edge > 68: - raise RuntimeError(self.OVERRUN) - width = ticks_diff(self._times[1], self._times[0]) - if width < self._leader: # 9ms leading mark for all valid data - raise RuntimeError(self.BADSTART) - width = ticks_diff(self._times[2], self._times[1]) - if width > 3000: # 4.5ms space for normal data - if self.edge < 68: # Haven't received the correct number of edges - raise RuntimeError(self.BADBLOCK) - # Time spaces only (marks are always 562.5µs) - # Space is 1.6875ms (1) or 562.5µs (0) - # Skip last bit which is always 1 - val = 0 - for edge in range(3, 68 - 2, 2): - val >>= 1 - if ticks_diff(self._times[edge + 1], self._times[edge]) > 1120: - val |= 0x80000000 - elif width > 1700: # 2.5ms space for a repeat code. Should have exactly 4 edges. - raise RuntimeError(self.REPEAT if self.edge == 4 else self.BADREP) # Treat REPEAT as error. - else: - raise RuntimeError(self.BADSTART) - addr = val & 0xff # 8 bit addr - cmd = (val >> 16) & 0xff - if cmd != (val >> 24) ^ 0xff: - raise RuntimeError(self.BADDATA) - if addr != ((val >> 8) ^ 0xff) & 0xff: # 8 bit addr doesn't match check - if not self._extended: - raise RuntimeError(self.BADADDR) - addr |= val & 0xff00 # pass assumed 16 bit address to callback - self._addr = addr - except RuntimeError as e: - cmd = e.args[0] - addr = self._addr if cmd == self.REPEAT else 0 # REPEAT uses last address - # Set up for new data burst and run user callback - self.do_callback(cmd, addr, 0, self.REPEAT) - -class NEC_8(NEC_ABC): - def __init__(self, pin, callback, *args): - super().__init__(pin, False, False, callback, *args) - -class NEC_16(NEC_ABC): - def __init__(self, pin, callback, *args): - super().__init__(pin, True, False, callback, *args) - -class SAMSUNG(NEC_ABC): - def __init__(self, pin, callback, *args): - super().__init__(pin, True, True, callback, *args) diff --git a/src/lib/ir_rx/philips.py b/src/lib/ir_rx/philips.py deleted file mode 100644 index cd7feaa..0000000 --- a/src/lib/ir_rx/philips.py +++ /dev/null @@ -1,123 +0,0 @@ -# philips.py Decoder for IR remote control using synchronous code -# Supports Philips RC-5 RC-6 mode 0 protocols. - -# Author: Peter Hinch -# Copyright Peter Hinch 2020 Released under the MIT license - -from utime import ticks_us, ticks_diff -from ir_rx import IR_RX - -class RC5_IR(IR_RX): - def __init__(self, pin, callback, *args): - # Block lasts <= 30ms and has <= 28 edges - super().__init__(pin, 28, 30, callback, *args) - - def decode(self, _): - try: - nedges = self.edge # No. of edges detected - if not 14 <= nedges <= 28: - raise RuntimeError(self.OVERRUN if nedges > 28 else self.BADSTART) - # Regenerate bitstream - bits = 1 - bit = 1 - v = 1 # 14 bit bitstream, MSB always 1 - x = 0 - while bits < 14: - # -1 convert count to index, -1 because we look ahead - if x > nedges - 2: - print('Bad block 1 edges', nedges, 'x', x) - raise RuntimeError(self.BADBLOCK) - # width is 889/1778 nominal - width = ticks_diff(self._times[x + 1], self._times[x]) - if not 500 < width < 2100: - self.verbose and print('Bad block 3 Width', width, 'x', x) - raise RuntimeError(self.BADBLOCK) - short = width < 1334 - if not short: - bit ^= 1 - v <<= 1 - v |= bit - bits += 1 - x += 1 + int(short) - self.verbose and print(bin(v)) - # Split into fields (val, addr, ctrl) - val = (v & 0x3f) | (0 if ((v >> 12) & 1) else 0x40) # Correct the polarity of S2 - addr = (v >> 6) & 0x1f - ctrl = (v >> 11) & 1 - - except RuntimeError as e: - val, addr, ctrl = e.args[0], 0, 0 - # Set up for new data burst and run user callback - self.do_callback(val, addr, ctrl) - - -class RC6_M0(IR_RX): - # Even on Pyboard D the 444μs nominal pulses can be recorded as up to 705μs - # Scope shows 360-520 μs (-84μs +76μs relative to nominal) - # Header nominal 2666, 889, 444, 889, 444, 444, 444, 444 carrier ON at end - hdr = ((1800, 4000), (593, 1333), (222, 750), (593, 1333), (222, 750), (222, 750), (222, 750), (222, 750)) - def __init__(self, pin, callback, *args): - # Block lasts 23ms nominal and has <=44 edges - super().__init__(pin, 44, 30, callback, *args) - - def decode(self, _): - try: - nedges = self.edge # No. of edges detected - if not 22 <= nedges <= 44: - raise RuntimeError(self.OVERRUN if nedges > 28 else self.BADSTART) - for x, lims in enumerate(self.hdr): - width = ticks_diff(self._times[x + 1], self._times[x]) - if not (lims[0] < width < lims[1]): - self.verbose and print('Bad start', x, width, lims) - raise RuntimeError(self.BADSTART) - x += 1 - width = ticks_diff(self._times[x + 1], self._times[x]) - # 2nd bit of last 0 is 444μs (0) or 1333μs (1) - if not 222 < width < 1555: - self.verbose and print('Bad block 1 Width', width, 'x', x) - raise RuntimeError(self.BADBLOCK) - short = width < 889 - v = int(not short) - bit = v - bits = 1 # Bits decoded - x += 1 + int(short) - width = ticks_diff(self._times[x + 1], self._times[x]) - if not 222 < width < 1555: - self.verbose and print('Bad block 2 Width', width, 'x', x) - raise RuntimeError(self.BADBLOCK) - short = width < 1111 - if not short: - bit ^= 1 - x += 1 + int(short) # If it's short, we know width of next - v <<= 1 - v |= bit # MSB of result - bits += 1 - # Decode bitstream - while bits < 17: - # -1 convert count to index, -1 because we look ahead - if x > nedges - 2: - raise RuntimeError(self.BADBLOCK) - # width is 444/889 nominal - width = ticks_diff(self._times[x + 1], self._times[x]) - if not 222 < width < 1111: - self.verbose and print('Bad block 3 Width', width, 'x', x) - raise RuntimeError(self.BADBLOCK) - short = width < 666 - if not short: - bit ^= 1 - v <<= 1 - v |= bit - bits += 1 - x += 1 + int(short) - - if self.verbose: - ss = '20-bit format {:020b} x={} nedges={} bits={}' - print(ss.format(v, x, nedges, bits)) - - val = v & 0xff - addr = (v >> 8) & 0xff - ctrl = (v >> 16) & 1 - except RuntimeError as e: - val, addr, ctrl = e.args[0], 0, 0 - # Set up for new data burst and run user callback - self.do_callback(val, addr, ctrl) diff --git a/src/lib/ir_rx/print_error.py b/src/lib/ir_rx/print_error.py deleted file mode 100644 index 31ce51e..0000000 --- a/src/lib/ir_rx/print_error.py +++ /dev/null @@ -1,19 +0,0 @@ -# print_error.py Error print for IR receiver - -# Author: Peter Hinch -# Copyright Peter Hinch 2020 Released under the MIT license - -from ir_rx import IR_RX - -_errors = {IR_RX.BADSTART : 'Invalid start pulse', - IR_RX.BADBLOCK : 'Error: bad block', - IR_RX.BADREP : 'Error: repeat', - IR_RX.OVERRUN : 'Error: overrun', - IR_RX.BADDATA : 'Error: invalid data', - IR_RX.BADADDR : 'Error: invalid address'} - -def print_error(data): - if data in _errors: - print(_errors[data]) - else: - print('Unknown error code:', data) diff --git a/src/lib/ir_rx/sony.py b/src/lib/ir_rx/sony.py deleted file mode 100644 index 1050356..0000000 --- a/src/lib/ir_rx/sony.py +++ /dev/null @@ -1,70 +0,0 @@ -# sony.py Decoder for IR remote control using synchronous code -# Sony SIRC protocol. - -# Author: Peter Hinch -# Copyright Peter Hinch 2020 Released under the MIT license - -from utime import ticks_us, ticks_diff -from ir_rx import IR_RX - -class SONY_ABC(IR_RX): # Abstract base class - def __init__(self, pin, bits, callback, *args): - # 20 bit block has 42 edges and lasts <= 39ms nominal. Add 4ms to time - # for tolerances except in 20 bit case where timing is tight with a - # repeat period of 45ms. - t = int(3 + bits * 1.8) + (1 if bits == 20 else 4) - super().__init__(pin, 2 + bits * 2, t, callback, *args) - self._addr = 0 - self._bits = 20 - - def decode(self, _): - try: - nedges = self.edge # No. of edges detected - self.verbose and print('nedges', nedges) - if nedges > 42: - raise RuntimeError(self.OVERRUN) - bits = (nedges - 2) // 2 - if nedges not in (26, 32, 42) or bits > self._bits: - raise RuntimeError(self.BADBLOCK) - self.verbose and print('SIRC {}bit'.format(bits)) - width = ticks_diff(self._times[1], self._times[0]) - if not 1800 < width < 3000: # 2.4ms leading mark for all valid data - raise RuntimeError(self.BADSTART) - width = ticks_diff(self._times[2], self._times[1]) - if not 350 < width < 1000: # 600μs space - raise RuntimeError(self.BADSTART) - - val = 0 # Data received, LSB 1st - x = 2 - bit = 1 - while x <= nedges - 2: - if ticks_diff(self._times[x + 1], self._times[x]) > 900: - val |= bit - bit <<= 1 - x += 2 - cmd = val & 0x7f # 7 bit command - val >>= 7 - if nedges < 42: - addr = val & 0xff # 5 or 8 bit addr - val = 0 - else: - addr = val & 0x1f # 5 bit addr - val >>= 5 # 8 bit extended - except RuntimeError as e: - cmd = e.args[0] - addr = 0 - val = 0 - self.do_callback(cmd, addr, val) - -class SONY_12(SONY_ABC): - def __init__(self, pin, callback, *args): - super().__init__(pin, 12, callback, *args) - -class SONY_15(SONY_ABC): - def __init__(self, pin, callback, *args): - super().__init__(pin, 15, callback, *args) - -class SONY_20(SONY_ABC): - def __init__(self, pin, callback, *args): - super().__init__(pin, 20, callback, *args) - diff --git a/src/lib/ir_rx/test.py b/src/lib/ir_rx/test.py deleted file mode 100644 index c4dbd7f..0000000 --- a/src/lib/ir_rx/test.py +++ /dev/null @@ -1,70 +0,0 @@ -# test.py Test program for IR remote control decoder -# Supports Pyboard, ESP32 and ESP8266 - -# Author: Peter Hinch -# Copyright Peter Hinch 2020-2022 Released under the MIT license - -# Run this to characterise a remote. - -from sys import platform -import time -import gc -from machine import Pin, freq -from ir_rx.print_error import print_error # Optional print of error codes - -# Import all implemented classes -from ir_rx.nec import NEC_8, NEC_16, SAMSUNG -from ir_rx.sony import SONY_12, SONY_15, SONY_20 -from ir_rx.philips import RC5_IR, RC6_M0 -from ir_rx.mce import MCE - -# Define pin according to platform -if platform == "pyboard": - p = Pin("X3", Pin.IN) -elif platform == "esp8266": - freq(160000000) - p = Pin(13, Pin.IN) -elif platform == "esp32" or platform == "esp32_LoBo": - p = Pin(23, Pin.IN) -elif platform == "rp2": - p = Pin(16, Pin.IN) - -# User callback -def cb(data, addr, ctrl): - if data < 0: # NEC protocol sends repeat codes. - print("Repeat code.") - else: - print(f"Data 0x{data:02x} Addr 0x{addr:04x} Ctrl 0x{ctrl:02x}") - - -def test(proto=0): - classes = (NEC_8, NEC_16, SONY_12, SONY_15, SONY_20, RC5_IR, RC6_M0, MCE, SAMSUNG) - ir = classes[proto](p, cb) # Instantiate receiver - ir.error_function(print_error) # Show debug information - # ir.verbose = True - # A real application would do something here... - try: - while True: - print("running") - time.sleep(5) - gc.collect() - except KeyboardInterrupt: - ir.close() - - -# **** DISPLAY GREETING **** -s = """Test for IR receiver. Run: -from ir_rx.test import test -test() for NEC 8 bit protocol, -test(1) for NEC 16 bit, -test(2) for Sony SIRC 12 bit, -test(3) for Sony SIRC 15 bit, -test(4) for Sony SIRC 20 bit, -test(5) for Philips RC-5 protocol, -test(6) for RC6 mode 0. -test(7) for Microsoft Vista MCE. -test(8) for Samsung. - -Hit ctrl-c to stop, then ctrl-d to soft reset.""" - -print(s) From 6f283a72980d0e3eb8a6fc092e131369d8683fc6 Mon Sep 17 00:00:00 2001 From: Jason Jackson Date: Sat, 30 Nov 2024 10:20:41 -0500 Subject: [PATCH 2/2] Added all libs again to hopefully get them to update --- src/lib/adafruit_bitmap_font/__init__.py | 0 src/lib/adafruit_bitmap_font/bdf.py | 233 +++++++ src/lib/adafruit_bitmap_font/bitmap_font.py | 62 ++ src/lib/adafruit_bitmap_font/glyph_cache.py | 56 ++ src/lib/adafruit_bitmap_font/pcf.py | 412 ++++++++++++ src/lib/adafruit_bitmap_font/ttf.py | 65 ++ src/lib/adafruit_bus_device/__init__.py | 0 src/lib/adafruit_bus_device/i2c_device.py | 187 ++++++ src/lib/adafruit_bus_device/spi_device.py | 121 ++++ src/lib/adafruit_connection_manager.mpy | Bin 0 -> 3541 bytes src/lib/adafruit_display_text/__init__.py | 472 ++++++++++++++ src/lib/adafruit_display_text/bitmap_label.py | 597 ++++++++++++++++++ src/lib/adafruit_display_text/label.py | 447 +++++++++++++ .../adafruit_display_text/outlined_label.py | 188 ++++++ .../adafruit_display_text/scrolling_label.py | 160 +++++ src/lib/adafruit_display_text/text_box.py | 435 +++++++++++++ src/lib/adafruit_ds3231.mpy | Bin 0 -> 1225 bytes src/lib/adafruit_imageload/__init__.py | 97 +++ src/lib/adafruit_imageload/bmp/__init__.py | 106 ++++ src/lib/adafruit_imageload/bmp/indexed.py | 265 ++++++++ .../bmp/negative_height_check.py | 19 + src/lib/adafruit_imageload/bmp/truecolor.py | 131 ++++ src/lib/adafruit_imageload/displayio_types.py | 28 + src/lib/adafruit_imageload/gif.py | 169 +++++ src/lib/adafruit_imageload/jpg.py | 57 ++ src/lib/adafruit_imageload/png.py | 184 ++++++ src/lib/adafruit_imageload/pnm/__init__.py | 138 ++++ src/lib/adafruit_imageload/pnm/pbm_ascii.py | 52 ++ src/lib/adafruit_imageload/pnm/pbm_binary.py | 74 +++ .../adafruit_imageload/pnm/pgm/__init__.py | 55 ++ src/lib/adafruit_imageload/pnm/pgm/ascii.py | 79 +++ src/lib/adafruit_imageload/pnm/pgm/binary.py | 67 ++ src/lib/adafruit_imageload/pnm/ppm_ascii.py | 98 +++ src/lib/adafruit_imageload/pnm/ppm_binary.py | 74 +++ .../adafruit_imageload/tilegrid_inflator.py | 109 ++++ src/lib/adafruit_irremote.mpy | Bin 0 -> 2977 bytes src/lib/adafruit_ntp.mpy | Bin 0 -> 1245 bytes src/lib/adafruit_register/__init__.py | 0 src/lib/adafruit_register/i2c_bcd_alarm.py | 202 ++++++ src/lib/adafruit_register/i2c_bcd_datetime.py | 114 ++++ src/lib/adafruit_register/i2c_bit.py | 84 +++ src/lib/adafruit_register/i2c_bits.py | 114 ++++ src/lib/adafruit_register/i2c_struct.py | 104 +++ src/lib/adafruit_register/i2c_struct_array.py | 114 ++++ src/lib/adafruit_requests.mpy | Bin 0 -> 6944 bytes src/lib/adafruit_ticks.mpy | Bin 0 -> 694 bytes src/lib/ir_rx/__init__.py | 70 ++ src/lib/ir_rx/acquire.py | 108 ++++ src/lib/ir_rx/mce.py | 68 ++ src/lib/ir_rx/nec.py | 69 ++ src/lib/ir_rx/philips.py | 123 ++++ src/lib/ir_rx/print_error.py | 19 + src/lib/ir_rx/sony.py | 70 ++ src/lib/ir_rx/test.py | 70 ++ 54 files changed, 6566 insertions(+) create mode 100644 src/lib/adafruit_bitmap_font/__init__.py create mode 100644 src/lib/adafruit_bitmap_font/bdf.py create mode 100644 src/lib/adafruit_bitmap_font/bitmap_font.py create mode 100644 src/lib/adafruit_bitmap_font/glyph_cache.py create mode 100644 src/lib/adafruit_bitmap_font/pcf.py create mode 100644 src/lib/adafruit_bitmap_font/ttf.py create mode 100644 src/lib/adafruit_bus_device/__init__.py create mode 100644 src/lib/adafruit_bus_device/i2c_device.py create mode 100644 src/lib/adafruit_bus_device/spi_device.py create mode 100644 src/lib/adafruit_connection_manager.mpy create mode 100644 src/lib/adafruit_display_text/__init__.py create mode 100644 src/lib/adafruit_display_text/bitmap_label.py create mode 100644 src/lib/adafruit_display_text/label.py create mode 100644 src/lib/adafruit_display_text/outlined_label.py create mode 100644 src/lib/adafruit_display_text/scrolling_label.py create mode 100644 src/lib/adafruit_display_text/text_box.py create mode 100644 src/lib/adafruit_ds3231.mpy create mode 100644 src/lib/adafruit_imageload/__init__.py create mode 100644 src/lib/adafruit_imageload/bmp/__init__.py create mode 100644 src/lib/adafruit_imageload/bmp/indexed.py create mode 100644 src/lib/adafruit_imageload/bmp/negative_height_check.py create mode 100644 src/lib/adafruit_imageload/bmp/truecolor.py create mode 100644 src/lib/adafruit_imageload/displayio_types.py create mode 100644 src/lib/adafruit_imageload/gif.py create mode 100644 src/lib/adafruit_imageload/jpg.py create mode 100644 src/lib/adafruit_imageload/png.py create mode 100644 src/lib/adafruit_imageload/pnm/__init__.py create mode 100644 src/lib/adafruit_imageload/pnm/pbm_ascii.py create mode 100644 src/lib/adafruit_imageload/pnm/pbm_binary.py create mode 100644 src/lib/adafruit_imageload/pnm/pgm/__init__.py create mode 100644 src/lib/adafruit_imageload/pnm/pgm/ascii.py create mode 100644 src/lib/adafruit_imageload/pnm/pgm/binary.py create mode 100644 src/lib/adafruit_imageload/pnm/ppm_ascii.py create mode 100644 src/lib/adafruit_imageload/pnm/ppm_binary.py create mode 100644 src/lib/adafruit_imageload/tilegrid_inflator.py create mode 100644 src/lib/adafruit_irremote.mpy create mode 100644 src/lib/adafruit_ntp.mpy create mode 100644 src/lib/adafruit_register/__init__.py create mode 100644 src/lib/adafruit_register/i2c_bcd_alarm.py create mode 100644 src/lib/adafruit_register/i2c_bcd_datetime.py create mode 100644 src/lib/adafruit_register/i2c_bit.py create mode 100644 src/lib/adafruit_register/i2c_bits.py create mode 100644 src/lib/adafruit_register/i2c_struct.py create mode 100644 src/lib/adafruit_register/i2c_struct_array.py create mode 100644 src/lib/adafruit_requests.mpy create mode 100644 src/lib/adafruit_ticks.mpy create mode 100644 src/lib/ir_rx/__init__.py create mode 100644 src/lib/ir_rx/acquire.py create mode 100644 src/lib/ir_rx/mce.py create mode 100644 src/lib/ir_rx/nec.py create mode 100644 src/lib/ir_rx/philips.py create mode 100644 src/lib/ir_rx/print_error.py create mode 100644 src/lib/ir_rx/sony.py create mode 100644 src/lib/ir_rx/test.py diff --git a/src/lib/adafruit_bitmap_font/__init__.py b/src/lib/adafruit_bitmap_font/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/adafruit_bitmap_font/bdf.py b/src/lib/adafruit_bitmap_font/bdf.py new file mode 100644 index 0000000..4ff14f0 --- /dev/null +++ b/src/lib/adafruit_bitmap_font/bdf.py @@ -0,0 +1,233 @@ +# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_bitmap_font.bdf` +==================================================== + +Loads BDF format fonts. + +* Author(s): Scott Shawcroft + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +try: + from typing import Union, Optional, Tuple, Iterable + from io import FileIO + from displayio import Bitmap +except ImportError: + pass + +import gc +from fontio import Glyph +from .glyph_cache import GlyphCache + +__version__ = "2.1.3" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font.git" + + +class BDF(GlyphCache): + """Loads glyphs from a BDF file in the given bitmap_class.""" + + def __init__(self, f: FileIO, bitmap_class: Bitmap) -> None: + super().__init__() + self.file = f + self.name = f + self.file.seek(0) + self.bitmap_class = bitmap_class + line = self._readline_file() + if not line or not line.startswith("STARTFONT 2.1"): + raise ValueError("Unsupported file version") + self._verify_bounding_box() + self.point_size = None + self.x_resolution = None + self.y_resolution = None + self._ascent = None + self._descent = None + + @property + def descent(self) -> Optional[int]: + """The number of pixels below the baseline of a typical descender""" + if self._descent is None: + self.file.seek(0) + while True: + line = self.file.readline() + if not line: + break + + if line.startswith(b"FONT_DESCENT "): + self._descent = int(line.split()[1]) + break + + return self._descent + + @property + def ascent(self) -> Optional[int]: + """The number of pixels above the baseline of a typical ascender""" + if self._ascent is None: + self.file.seek(0) + while True: + line = self._readline_file() + if not line: + break + + if line.startswith("FONT_ASCENT "): + self._ascent = int(line.split()[1]) + break + + return self._ascent + + def _verify_bounding_box(self) -> None: + """Private function to verify FOUNTBOUNDINGBOX parameter + This function will parse the first 10 lines of the font source + file to verify the value or raise an exception in case is not found + """ + self.file.seek(0) + # Normally information about the FONT is in the first four lines. + # Exception is when font file have a comment. Comments are three lines + # 10 lines is a safe bet + for _ in range(11): + line = self._readline_file() + while line.startswith("COMMENT "): + line = self._readline_file() + if line.startswith("FONTBOUNDINGBOX "): + _, x, y, x_offset, y_offset = line.split() + self._boundingbox = (int(x), int(y), int(x_offset), int(y_offset)) + + try: + self._boundingbox + except AttributeError as error: + raise RuntimeError( + "Source file does not have the FOUNTBOUNDINGBOX parameter" + ) from error + + def _readline_file(self) -> str: + line = self.file.readline() + return str(line, "utf-8") + + def get_bounding_box(self) -> Tuple[int, int, int, int]: + """Return the maximum glyph size as a 4-tuple of: width, height, x_offset, y_offset""" + return self._boundingbox + + def load_glyphs(self, code_points: Union[int, str, Iterable[int]]) -> None: + # pylint: disable=too-many-statements,too-many-branches,too-many-nested-blocks,too-many-locals + metadata = True + character = False + code_point = None + bytes_per_row = 1 + desired_character = False + current_info = {} + current_y = 0 + rounded_x = 1 + if isinstance(code_points, int): + remaining = set() + remaining.add(code_points) + elif isinstance(code_points, str): + remaining = set(ord(c) for c in code_points) + elif isinstance(code_points, set): + remaining = code_points + else: + remaining = set(code_points) + for code_point in remaining.copy(): + if code_point in self._glyphs and self._glyphs[code_point]: + remaining.remove(code_point) + if not remaining: + return + + x, _, _, _ = self._boundingbox + + self.file.seek(0) + while True: + line = self.file.readline() + if not line: + break + if line.startswith(b"CHARS "): + metadata = False + elif line.startswith(b"SIZE"): + _, self.point_size, self.x_resolution, self.y_resolution = line.split() + elif line.startswith(b"COMMENT"): + pass + elif line.startswith(b"STARTCHAR"): + character = True + elif line.startswith(b"ENDCHAR"): + character = False + if desired_character: + bounds = current_info["bounds"] + shift = current_info["shift"] + gc.collect() + self._glyphs[code_point] = Glyph( + current_info["bitmap"], + 0, + bounds[0], + bounds[1], + bounds[2], + bounds[3], + shift[0], + shift[1], + ) + remaining.remove(code_point) + if not remaining: + return + desired_character = False + elif line.startswith(b"BBX"): + if desired_character: + _, x, y, x_offset, y_offset = line.split() + x = int(x) + y = int(y) + x_offset = int(x_offset) + y_offset = int(y_offset) + current_info["bounds"] = (x, y, x_offset, y_offset) + current_info["bitmap"] = self.bitmap_class(x, y, 2) + elif line.startswith(b"BITMAP"): + if desired_character: + rounded_x = x // 8 + if x % 8 > 0: + rounded_x += 1 + bytes_per_row = rounded_x + if bytes_per_row % 4 > 0: + bytes_per_row += 4 - bytes_per_row % 4 + current_y = 0 + elif line.startswith(b"ENCODING"): + _, code_point = line.split() + code_point = int(code_point) + if code_point in remaining: + desired_character = True + current_info = {"bitmap": None, "bounds": None, "shift": None} + elif line.startswith(b"DWIDTH"): + if desired_character: + _, shift_x, shift_y = line.split() + shift_x = int(shift_x) + shift_y = int(shift_y) + current_info["shift"] = (shift_x, shift_y) + elif line.startswith(b"SWIDTH"): + pass + elif character: + if desired_character: + bits = int(line.strip(), 16) + width = current_info["bounds"][0] + start = current_y * width + x = 0 + for i in range(rounded_x): + val = (bits >> ((rounded_x - i - 1) * 8)) & 0xFF + for j in range(7, -1, -1): + if x >= width: + break + bit = 0 + if val & (1 << j) != 0: + bit = 1 + current_info["bitmap"][start + x] = bit + x += 1 + current_y += 1 + elif metadata: + pass diff --git a/src/lib/adafruit_bitmap_font/bitmap_font.py b/src/lib/adafruit_bitmap_font/bitmap_font.py new file mode 100644 index 0000000..640b041 --- /dev/null +++ b/src/lib/adafruit_bitmap_font/bitmap_font.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_bitmap_font.bitmap_font` +==================================================== + +Loads bitmap glyphs from a variety of font. + +* Author(s): Scott Shawcroft + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +try: + from typing import Optional, Union + from displayio import Bitmap + from . import bdf + from . import pcf + from . import ttf +except ImportError: + pass + +__version__ = "2.1.3" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font.git" + + +def load_font( + filename: str, bitmap: Optional[Bitmap] = None +) -> Union[bdf.BDF, pcf.PCF, ttf.TTF]: + """Loads a font file. Returns None if unsupported.""" + # pylint: disable=import-outside-toplevel, redefined-outer-name, consider-using-with + if not bitmap: + import displayio + + bitmap = displayio.Bitmap + font_file = open(filename, "rb") + first_four = font_file.read(4) + if filename.endswith("bdf") and first_four == b"STAR": + from . import bdf + + return bdf.BDF(font_file, bitmap) + if filename.endswith("pcf") and first_four == b"\x01fcp": + from . import pcf + + return pcf.PCF(font_file, bitmap) + if filename.endswith("ttf") and first_four == b"\x00\x01\x00\x00": + from . import ttf + + return ttf.TTF(font_file, bitmap) + + raise ValueError("Unknown magic number %r" % first_four) diff --git a/src/lib/adafruit_bitmap_font/glyph_cache.py b/src/lib/adafruit_bitmap_font/glyph_cache.py new file mode 100644 index 0000000..2933f89 --- /dev/null +++ b/src/lib/adafruit_bitmap_font/glyph_cache.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_bitmap_font.glyph_cache` +==================================================== + +Displays text using CircuitPython's displayio. + +* Author(s): Scott Shawcroft + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +try: + from typing import Union, Iterable + from fontio import Glyph +except ImportError: + pass + +import gc + +__version__ = "2.1.3" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font.git" + + +class GlyphCache: + """Caches glyphs loaded by a subclass.""" + + def __init__(self) -> None: + self._glyphs = {} + + def load_glyphs(self, code_points: Union[int, str, Iterable[int]]) -> None: + """Loads displayio.Glyph objects into the GlyphCache from the font.""" + + def get_glyph(self, code_point: int) -> Glyph: + """Returns a displayio.Glyph for the given code point or None is unsupported.""" + if code_point in self._glyphs: + return self._glyphs[code_point] + + code_points = set() + code_points.add(code_point) + self._glyphs[code_point] = None + self.load_glyphs(code_points) + gc.collect() + return self._glyphs[code_point] diff --git a/src/lib/adafruit_bitmap_font/pcf.py b/src/lib/adafruit_bitmap_font/pcf.py new file mode 100644 index 0000000..860ca09 --- /dev/null +++ b/src/lib/adafruit_bitmap_font/pcf.py @@ -0,0 +1,412 @@ +# SPDX-FileCopyrightText: 2020 Jeff Epler for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_bitmap_font.pcf` +==================================================== + +Loads PCF format fonts. + +* Author(s): Jeff Epler + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +try: + from typing import Union, Tuple, Iterator, Iterable + from io import FileIO + from displayio import Bitmap as displayioBitmap +except ImportError: + pass + +from collections import namedtuple +import gc +import struct +from micropython import const +from fontio import Glyph +from .glyph_cache import GlyphCache + +try: + from bitmaptools import readinto as _bitmap_readinto +except ImportError: + _bitmap_readinto = None # pylint: disable=invalid-name + +_PCF_PROPERTIES = const(1 << 0) +_PCF_ACCELERATORS = const(1 << 1) +_PCF_METRICS = const(1 << 2) +_PCF_BITMAPS = const(1 << 3) +_PCF_INK_METRICS = const(1 << 4) +_PCF_BDF_ENCODINGS = const(1 << 5) +_PCF_SWIDTHS = const(1 << 6) +_PCF_GLYPH_NAMES = const(1 << 7) +_PCF_BDF_ACCELERATORS = const(1 << 8) + +_PCF_DEFAULT_FORMAT = const(0x00000000) +_PCF_ACCEL_W_INKBOUNDS = const(0x00000100) +_PCF_COMPRESSED_METRICS = const(0x00000100) + +_PCF_GLYPH_PAD_MASK = const(3 << 0) # See the bitmap table for explanation */ +_PCF_BYTE_MASK = const(1 << 2) # If set then Most Sig Byte First */ +_PCF_BIT_MASK = const(1 << 3) # If set then Most Sig Bit First */ +_PCF_SCAN_UNIT_MASK = const(3 << 4) + +# https://fontforge.org/docs/techref/pcf-format.html + +Table = namedtuple("Table", ("format", "size", "offset")) +Metrics = namedtuple( + "Metrics", + ( + "left_side_bearing", + "right_side_bearing", + "character_width", + "character_ascent", + "character_descent", + "character_attributes", + ), +) +Accelerators = namedtuple( + "Accelerators", + ( + "no_overlap", + "constant_metrics", + "terminal_font", + "constant_width", + "ink_inside", + "ink_metrics", + "draw_direction", + "font_ascent", + "font_descent", + "max_overlap", + "minbounds", + "maxbounds", + "ink_minbounds", + "ink_maxbounds", + ), +) +Encoding = namedtuple( + "Encoding", ("min_byte2", "max_byte2", "min_byte1", "max_byte1", "default_char") +) +Bitmap = namedtuple("Bitmap", ("glyph_count", "bitmap_sizes")) + + +class PCF(GlyphCache): + """Loads glyphs from a PCF file in the given bitmap_class.""" + + def __init__(self, f: FileIO, bitmap_class: displayioBitmap) -> None: + super().__init__() + self.file = f + self.name = f + f.seek(0) + self.buffer = bytearray(1) + self.bitmap_class = bitmap_class + _, table_count = self._read("<4sI") + self.tables = {} + for _ in range(table_count): + type_, format_, size, offset = self._read(" int: + """The number of pixels above the baseline of a typical ascender""" + return self._ascent + + @property + def descent(self) -> int: + """The number of pixels below the baseline of a typical descender""" + return self._descent + + def get_bounding_box(self) -> Tuple[int, int, int, int]: + """Return the maximum glyph size as a 4-tuple of: width, height, x_offset, y_offset""" + return self._bounding_box + + def _read(self, format_: str) -> Tuple: + size = struct.calcsize(format_) + if size != len(self.buffer): + self.buffer = bytearray(size) + self.file.readinto(self.buffer) + return struct.unpack_from(format_, self.buffer) + + def _seek_table(self, table: Table) -> int: + self.file.seek(table.offset) + (format_,) = self._read(" Encoding: + encoding = self.tables[_PCF_BDF_ENCODINGS] + self._seek_table(encoding) + + return Encoding(*self._read(">hhhhh")) + + def _read_bitmap_table(self) -> Bitmap: + bitmaps = self.tables[_PCF_BITMAPS] + format_ = self._seek_table(bitmaps) + + (glyph_count,) = self._read(">I") + self.file.seek(bitmaps.offset + 8 + 4 * glyph_count) + bitmap_sizes = self._read(">4I") + return Bitmap(glyph_count, bitmap_sizes[format_ & 3]) + + def _read_metrics(self, compressed_metrics: bool) -> Metrics: + if compressed_metrics: + ( + left_side_bearing, + right_side_bearing, + character_width, + character_ascent, + character_descent, + ) = self._read("5B") + left_side_bearing -= 0x80 + right_side_bearing -= 0x80 + character_width -= 0x80 + character_ascent -= 0x80 + character_descent -= 0x80 + attributes = 0 + else: + ( + left_side_bearing, + right_side_bearing, + character_width, + character_ascent, + character_descent, + attributes, + ) = self._read(">5hH") + return Metrics( + left_side_bearing, + right_side_bearing, + character_width, + character_ascent, + character_descent, + attributes, + ) + + def _read_accelerator_tables(self) -> Accelerators: + # pylint: disable=too-many-locals + accelerators = self.tables.get(_PCF_BDF_ACCELERATORS) + if not accelerators: + accelerators = self.tables.get(_PCF_ACCELERATORS) + if not accelerators: + raise RuntimeError("Accelerator table missing") + + format_ = self._seek_table(accelerators) + has_inkbounds = format_ & _PCF_ACCEL_W_INKBOUNDS + + ( + no_overlap, + constant_metrics, + terminal_font, + constant_width, + ink_inside, + ink_metrics, + draw_direction, + _, + font_ascent, + font_descent, + max_overlap, + ) = self._read(">BBBBBBBBIII") + minbounds = self._read_metrics(False) + maxbounds = self._read_metrics(False) + if has_inkbounds: + ink_minbounds = self._read_metrics(False) + ink_maxbounds = self._read_metrics(False) + else: + ink_minbounds = minbounds + ink_maxbounds = maxbounds + + return Accelerators( + no_overlap, + constant_metrics, + terminal_font, + constant_width, + ink_inside, + ink_metrics, + draw_direction, + font_ascent, + font_descent, + max_overlap, + minbounds, + maxbounds, + ink_minbounds, + ink_maxbounds, + ) + + def _read_properties(self) -> Iterator[Tuple[bytes, Union[bytes, int]]]: + property_table_offset = self.tables[_PCF_PROPERTIES]["offset"] + self.file.seek(property_table_offset) + (format_,) = self._read("I") + self.file.seek(property_table_offset + 8 + 9 * nprops) + + pos = self.file.tell() + if pos % 4 > 0: + self.file.read(4 - pos % 4) + (string_size,) = self._read(">I") + + strings = self.file.read(string_size) + string_map = {} + i = 0 + for value in strings.split(b"\x00"): + string_map[i] = value + i += len(value) + 1 + + self.file.seek(property_table_offset + 8) + for _ in range(nprops): + name_offset, is_string_prop, value = self._read(">IBI") + + if is_string_prop: + yield (string_map[name_offset], string_map[value]) + else: + yield (string_map[name_offset], value) + + def load_glyphs(self, code_points: Union[int, str, Iterable[int]]) -> None: + # pylint: disable=too-many-statements,too-many-branches,too-many-nested-blocks,too-many-locals + if isinstance(code_points, int): + code_points = (code_points,) + elif isinstance(code_points, str): + code_points = [ord(c) for c in code_points] + + code_points = sorted( + c for c in code_points if self._glyphs.get(c, None) is None + ) + if not code_points: + return + + indices_offset = self.tables[_PCF_BDF_ENCODINGS].offset + 14 + bitmap_offset_offsets = self.tables[_PCF_BITMAPS].offset + 8 + first_bitmap_offset = self.tables[_PCF_BITMAPS].offset + 4 * ( + 6 + self._bitmaps.glyph_count + ) + metrics_compressed = self.tables[_PCF_METRICS].format & _PCF_COMPRESSED_METRICS + first_metric_offset = self.tables[_PCF_METRICS].offset + ( + 6 if metrics_compressed else 8 + ) + metrics_size = 5 if metrics_compressed else 12 + + # These will each _tend to be_ forward reads in the file, at least + # sometimes we'll benefit from oofatfs's 512 byte cache and avoid + # excess reads + indices = [None] * len(code_points) + for i, code_point in enumerate(code_points): + enc1 = (code_point >> 8) & 0xFF + enc2 = code_point & 0xFF + + if enc1 < self._encoding.min_byte1 or enc1 > self._encoding.max_byte1: + continue + if enc2 < self._encoding.min_byte2 or enc2 > self._encoding.max_byte2: + continue + + encoding_idx = ( + (enc1 - self._encoding.min_byte1) + * (self._encoding.max_byte2 - self._encoding.min_byte2 + 1) + + enc2 + - self._encoding.min_byte2 + ) + self.file.seek(indices_offset + 2 * encoding_idx) + (glyph_idx,) = self._read(">H") + if glyph_idx != 65535: + indices[i] = glyph_idx + + all_metrics = [None] * len(code_points) + for i, code_point in enumerate(code_points): + index = indices[i] + if index is None: + continue + self.file.seek(first_metric_offset + metrics_size * index) + all_metrics[i] = self._read_metrics(metrics_compressed) + bitmap_offsets = [None] * len(code_points) + for i, code_point in enumerate(code_points): + index = indices[i] + if index is None: + continue + self.file.seek(bitmap_offset_offsets + 4 * index) + (bitmap_offset,) = self._read(">I") + bitmap_offsets[i] = bitmap_offset + + # Batch creation of glyphs and bitmaps so that we need only gc.collect + # once + gc.collect() + bitmaps = [None] * len(code_points) + for i in range(len(all_metrics)): # pylint: disable=consider-using-enumerate + metrics = all_metrics[i] + if metrics is not None: + width = metrics.right_side_bearing - metrics.left_side_bearing + height = metrics.character_ascent + metrics.character_descent + bitmap = bitmaps[i] = self.bitmap_class(width, height, 2) + self._glyphs[code_points[i]] = Glyph( + bitmap, + 0, + width, + height, + metrics.left_side_bearing, + -metrics.character_descent, + metrics.character_width, + 0, + ) + + for i, code_point in enumerate(code_points): + metrics = all_metrics[i] + if metrics is None: + continue + self.file.seek(first_bitmap_offset + bitmap_offsets[i]) + width = metrics.right_side_bearing - metrics.left_side_bearing + height = metrics.character_ascent + metrics.character_descent + + bitmap = bitmaps[i] + + if _bitmap_readinto: + _bitmap_readinto( + bitmap, + self.file, + bits_per_pixel=1, + element_size=4, + reverse_pixels_in_element=True, + ) + else: + words_per_row = (width + 31) // 32 + buf = bytearray(4 * words_per_row) + start = 0 + for _ in range(height): + self.file.readinto(buf) + for k in range(width): + if buf[k // 8] & (128 >> (k % 8)): + bitmap[start + k] = 1 + start += width diff --git a/src/lib/adafruit_bitmap_font/ttf.py b/src/lib/adafruit_bitmap_font/ttf.py new file mode 100644 index 0000000..807ac79 --- /dev/null +++ b/src/lib/adafruit_bitmap_font/ttf.py @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +# pylint: skip-file +# Remove the above when TTF is actually supported. + +try: + from typing import Tuple + from io import FileIO + from displayio import Bitmap +except ImportError: + pass + +import struct + +# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html + + +class TTF: + def __init__(self, f: FileIO, bitmap: Bitmap) -> None: + f.seek(0) + self.file = f + + self.characters = {} + + def read(format: str) -> Tuple: + s = struct.calcsize(format) + return struct.unpack_from(format, f.read(s)) + + scalar_type = read(">I") + numTables, searchRange, entrySelector, rangeShift = read(">HHHH") + + print(numTables) + table_info = {} + for _ in range(numTables): + tag, checkSum, offset, length = read(">4sIII") + print(tag.decode("utf-8"), hex(checkSum), offset, length) + table_info[tag] = (offset, length) + + head_offset, head_length = table_info[b"head"] + f.seek(head_offset) + version, fontRevision, checkSumAdjustment, magicNumber = read(">IIII") + flags, unitsPerEm, created, modified = read(">HHQQ") + xMin, yMin, xMax, yMax = read(">hhhh") + print(xMin, yMin, xMax, yMax) + macStyle, lowestRecPPEM, fontDirectionHint = read(">HHh") + indexToLocFormat, glyphDataFormat = read(">hh") + + glyf_offset, glyf_length = table_info[b"glyf"] + f.seek(glyf_offset) + while f.tell() < glyf_offset + glyf_length: + numberOfContours, xMin, yMin, xMax, yMax = read(">hhhhh") + + if numberOfContours > 0: # Simple + print(numberOfContours) + ends = [] + for _ in range(numberOfContours): + ends.append(read(">H")) + instructionLength = read(">h")[0] + instructions = read(">{}s".format(instructionLength))[0] + print(instructions) + break + else: + raise RuntimeError("Unsupported font") diff --git a/src/lib/adafruit_bus_device/__init__.py b/src/lib/adafruit_bus_device/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/adafruit_bus_device/i2c_device.py b/src/lib/adafruit_bus_device/i2c_device.py new file mode 100644 index 0000000..c605290 --- /dev/null +++ b/src/lib/adafruit_bus_device/i2c_device.py @@ -0,0 +1,187 @@ +# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_bus_device.i2c_device` - I2C Bus Device +==================================================== +""" + +import time + +try: + from typing import Optional, Type + from types import TracebackType + from circuitpython_typing import ReadableBuffer, WriteableBuffer + + # Used only for type annotations. + from busio import I2C +except ImportError: + pass + + +__version__ = "5.2.10" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BusDevice.git" + + +class I2CDevice: + """ + Represents a single I2C device and manages locking the bus and the device + address. + + :param ~busio.I2C i2c: The I2C bus the device is on + :param int device_address: The 7 bit device address + :param bool probe: Probe for the device upon object creation, default is true + + .. note:: This class is **NOT** built into CircuitPython. See + :ref:`here for install instructions `. + + Example: + + .. code-block:: python + + import busio + from board import * + from adafruit_bus_device.i2c_device import I2CDevice + + with busio.I2C(SCL, SDA) as i2c: + device = I2CDevice(i2c, 0x70) + bytes_read = bytearray(4) + with device: + device.readinto(bytes_read) + # A second transaction + with device: + device.write(bytes_read) + """ + + def __init__(self, i2c: I2C, device_address: int, probe: bool = True) -> None: + self.i2c = i2c + self.device_address = device_address + + if probe: + self.__probe_for_device() + + def readinto( + self, buf: WriteableBuffer, *, start: int = 0, end: Optional[int] = None + ) -> None: + """ + Read into ``buf`` from the device. The number of bytes read will be the + length of ``buf``. + + If ``start`` or ``end`` is provided, then the buffer will be sliced + as if ``buf[start:end]``. This will not cause an allocation like + ``buf[start:end]`` will so it saves memory. + + :param ~WriteableBuffer buffer: buffer to write into + :param int start: Index to start writing at + :param int end: Index to write up to but not include; if None, use ``len(buf)`` + """ + if end is None: + end = len(buf) + self.i2c.readfrom_into(self.device_address, buf, start=start, end=end) + + def write( + self, buf: ReadableBuffer, *, start: int = 0, end: Optional[int] = None + ) -> None: + """ + Write the bytes from ``buffer`` to the device, then transmit a stop + bit. + + If ``start`` or ``end`` is provided, then the buffer will be sliced + as if ``buffer[start:end]``. This will not cause an allocation like + ``buffer[start:end]`` will so it saves memory. + + :param ~ReadableBuffer buffer: buffer containing the bytes to write + :param int start: Index to start writing from + :param int end: Index to read up to but not include; if None, use ``len(buf)`` + """ + if end is None: + end = len(buf) + self.i2c.writeto(self.device_address, buf, start=start, end=end) + + # pylint: disable-msg=too-many-arguments + def write_then_readinto( + self, + out_buffer: ReadableBuffer, + in_buffer: WriteableBuffer, + *, + out_start: int = 0, + out_end: Optional[int] = None, + in_start: int = 0, + in_end: Optional[int] = None + ) -> None: + """ + Write the bytes from ``out_buffer`` to the device, then immediately + reads into ``in_buffer`` from the device. The number of bytes read + will be the length of ``in_buffer``. + + If ``out_start`` or ``out_end`` is provided, then the output buffer + will be sliced as if ``out_buffer[out_start:out_end]``. This will + not cause an allocation like ``buffer[out_start:out_end]`` will so + it saves memory. + + If ``in_start`` or ``in_end`` is provided, then the input buffer + will be sliced as if ``in_buffer[in_start:in_end]``. This will not + cause an allocation like ``in_buffer[in_start:in_end]`` will so + it saves memory. + + :param ~ReadableBuffer out_buffer: buffer containing the bytes to write + :param ~WriteableBuffer in_buffer: buffer containing the bytes to read into + :param int out_start: Index to start writing from + :param int out_end: Index to read up to but not include; if None, use ``len(out_buffer)`` + :param int in_start: Index to start writing at + :param int in_end: Index to write up to but not include; if None, use ``len(in_buffer)`` + """ + if out_end is None: + out_end = len(out_buffer) + if in_end is None: + in_end = len(in_buffer) + + self.i2c.writeto_then_readfrom( + self.device_address, + out_buffer, + in_buffer, + out_start=out_start, + out_end=out_end, + in_start=in_start, + in_end=in_end, + ) + + # pylint: enable-msg=too-many-arguments + + def __enter__(self) -> "I2CDevice": + while not self.i2c.try_lock(): + time.sleep(0) + return self + + def __exit__( + self, + exc_type: Optional[Type[type]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> bool: + self.i2c.unlock() + return False + + def __probe_for_device(self) -> None: + """ + Try to read a byte from an address, + if you get an OSError it means the device is not there + or that the device does not support these means of probing + """ + while not self.i2c.try_lock(): + time.sleep(0) + try: + self.i2c.writeto(self.device_address, b"") + except OSError: + # some OS's dont like writing an empty bytesting... + # Retry by reading a byte + try: + result = bytearray(1) + self.i2c.readfrom_into(self.device_address, result) + except OSError: + # pylint: disable=raise-missing-from + raise ValueError("No I2C device at address: 0x%x" % self.device_address) + # pylint: enable=raise-missing-from + finally: + self.i2c.unlock() diff --git a/src/lib/adafruit_bus_device/spi_device.py b/src/lib/adafruit_bus_device/spi_device.py new file mode 100644 index 0000000..60954e0 --- /dev/null +++ b/src/lib/adafruit_bus_device/spi_device.py @@ -0,0 +1,121 @@ +# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +# pylint: disable=too-few-public-methods + +""" +`adafruit_bus_device.spi_device` - SPI Bus Device +==================================================== +""" + +import time + +try: + from typing import Optional, Type + from types import TracebackType + + # Used only for type annotations. + from busio import SPI + from digitalio import DigitalInOut +except ImportError: + pass + + +__version__ = "5.2.10" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BusDevice.git" + + +class SPIDevice: + """ + Represents a single SPI device and manages locking the bus and the device + address. + + :param ~busio.SPI spi: The SPI bus the device is on + :param ~digitalio.DigitalInOut chip_select: The chip select pin object that implements the + DigitalInOut API. + :param bool cs_active_value: Set to True if your device requires CS to be active high. + Defaults to False. + :param int baudrate: The desired SCK clock rate in Hertz. The actual clock rate may be + higher or lower due to the granularity of available clock settings (MCU dependent). + :param int polarity: The base state of the SCK clock pin (0 or 1). + :param int phase: The edge of the clock that data is captured. First (0) or second (1). + Rising or falling depends on SCK clock polarity. + :param int extra_clocks: The minimum number of clock cycles to cycle the bus after CS is high. + (Used for SD cards.) + + .. note:: This class is **NOT** built into CircuitPython. See + :ref:`here for install instructions `. + + Example: + + .. code-block:: python + + import busio + import digitalio + from board import * + from adafruit_bus_device.spi_device import SPIDevice + + with busio.SPI(SCK, MOSI, MISO) as spi_bus: + cs = digitalio.DigitalInOut(D10) + device = SPIDevice(spi_bus, cs) + bytes_read = bytearray(4) + # The object assigned to spi in the with statements below + # is the original spi_bus object. We are using the busio.SPI + # operations busio.SPI.readinto() and busio.SPI.write(). + with device as spi: + spi.readinto(bytes_read) + # A second transaction + with device as spi: + spi.write(bytes_read) + """ + + def __init__( + self, + spi: SPI, + chip_select: Optional[DigitalInOut] = None, + *, + cs_active_value: bool = False, + baudrate: int = 100000, + polarity: int = 0, + phase: int = 0, + extra_clocks: int = 0 + ) -> None: + self.spi = spi + self.baudrate = baudrate + self.polarity = polarity + self.phase = phase + self.extra_clocks = extra_clocks + self.chip_select = chip_select + self.cs_active_value = cs_active_value + if self.chip_select: + self.chip_select.switch_to_output(value=not self.cs_active_value) + + def __enter__(self) -> SPI: + while not self.spi.try_lock(): + time.sleep(0) + self.spi.configure( + baudrate=self.baudrate, polarity=self.polarity, phase=self.phase + ) + if self.chip_select: + self.chip_select.value = self.cs_active_value + return self.spi + + def __exit__( + self, + exc_type: Optional[Type[type]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> bool: + if self.chip_select: + self.chip_select.value = not self.cs_active_value + if self.extra_clocks > 0: + buf = bytearray(1) + buf[0] = 0xFF + clocks = self.extra_clocks // 8 + if self.extra_clocks % 8 != 0: + clocks += 1 + for _ in range(clocks): + self.spi.write(buf) + self.spi.unlock() + return False diff --git a/src/lib/adafruit_connection_manager.mpy b/src/lib/adafruit_connection_manager.mpy new file mode 100644 index 0000000000000000000000000000000000000000..002d5cd8538342456ab2f6a200fa11ca3a85fb73 GIT binary patch literal 3541 zcmaJ?-%}gc6}}P(gbaAuU2glSstxjS)ndwufozhO?_fG$Z_Ux`?u;VE++P!t7UGJt4KLD9GNS*O%9poOQM9ZrK)Pnm60X21yQJ5+OC!=MKl&J2`uD^Z%gZZ zCCRfJoRr$Ga%dn{kvO4{V>#{9t-Go`UtZwzTV>r!p4wnbCg;OEY`Uid1Xvxpsv$Fa zzA;hDfNackZi9mzIsm%R`feg1TUckb$Vqp3VFL|M4qxYPa0Qa zyv@XUu8`X*Yc109-hA0KPOMJ&#A<2mXBOrZ=9T~J#a3cB79*+2Idp=1xAdC|Crxgg z;I=Gp#(k5w_YNnBsy?XXQh1I@rj{eu7f`p(3)MzEr|NR5sJ&8YljFA})LjzQY9s1& z$*(}|Uy~`rmU)q5a^*6rrE3=ln1aATpS1pq$Wq`1mP56VylT3i??#T^W-PKWn|z*w zZg5hI!_GOgz_E8w?JTao)n@aEJk@h+b2FngwYQ0nx+9BA3lTUiQ&N?UsJlY0+RB5a z=9A3A5*cc6nbjs3EGrtorerW!;JT8G$RpC~`vU zwA@P!kEYNj$rJMhkrSX<%sMYh+AMotrv*@36$!cZo1ul*wC)(FQzb_oxqKdxYPFl3 zy4NMCDo!B^Y~T}0l>(0_hH2`9VGzau=_(JaD=jfHFtbcD9gi<9r48L;9H>v=3Y@@ISWZOT4uMH!Q5REfyjMLkCyFSyGrrSb%hr;(Y5@&F z70hbMk-5qX63W(AEC8$uOoVv#A}-~TX-2Q!_;|4-t#7T3vHa$EYo5oiw{GuOZdWG! za!8tY*cfa^7GtO3-A==0Q_m|mCNlNBdXJfUBgr_EjK@GIk^uLR>3q5(ZdFy5T;8q~ z!p@d+qBv#$_!DGqyF7NuL?yL>Q|~Q%g_rD2r6ALAYP}>jH|2Rw)VxhJJ50j(Oez&m zA``9yyt>(cy<+F?ftul8h+XfC9nf-RxRLYQ_LoYwOFU2+AOHB1DPUw8Idx^&H@8H| zzQ)<1ws%T-I6$dj-x4)IxLv-Sch4Gb+ht;ic23iyt8ahab)UN5yGxerLqkLP-Ok;} zZqNO%?hj%{D(OH}N=}?_quSuvPPID>RJyO7FcBRN<8k=6GS-Qiu`W;Bd5f)+>a>|D zv#pEj%F5UotQ+gWda<(}(|OExhB{;Grn+rCRFAEf>b0Gv&Sq0Sg!TE2RA0(xz;IaL zJ|lH5e4Q8xL1ZHCF(xO>)J0zxHIZN~ zwRvRe+w<=B^z~CWus-TTAENv|19dII8h5J`Hh8F>w60hu8;UhhC*D!4 z@6?GaiuFpJcvrE$TPLOz>r|b1PqDs-m+IL234fbneP3N()s|Ni>}RV#7_n=`U&~YW z!SqbGLQIm^>IxAE7}*uP@Pf`hiJiqa@HXNDyxi~`{KiHh@x*PsHEXz)U~74^8Q%-# zHJ-T6GvP5LCk)he;JQ9w@fUB&VPF_cusbtjP@Xif^T+fJ(2(sLc>Lnq1M-3rFnqU9 zz8yeMLw~3>9PP*SD!A;#FI!Ih+@az(J`H_VYhZ)N^gm>9JBXo{JMk3Pg!oK*4L?65 zoV8<`I|5VglznX`CSJA0b_g0_0ey=TE) z%bq14+`F(ITo3N{?sk6OsWqI`8y?piehG9xqcy*ciuL7v?S2C_2X7O+=Rxk%zD{Zp z{9fefz&vYlY;-MwdDlSp@dVqoV;;c##hdb9XxA{v@A6dN4HW{SK^ z{>knH)zOVi1A^n0o3pL;)71@I|9Ppje7>Bi>?P}ZU?6!LoDE8FjnDgJrj)JnSsHLkILUUOs>l*Bko>p7!UD zfWwb;0iKW2-#^xf%xOd(1Chrp@V1cd{dyzN2K4u_#4#OOe)^D}Jqkxbp=ZFT)_J+! zkl{@@1MPD9hZ#yCV_(-BhYybe9p5YD%%jHvBYh4K1bhF$V*V&H%I4T z_ List[str]: + # pylint: disable=too-many-branches, too-many-locals, too-many-nested-blocks, too-many-statements + + """wrap_text_to_pixels function + A helper that will return a list of lines with word-break wrapping. + Leading and trailing whitespace in your string will be removed. If + you wish to use leading whitespace see ``indent0`` and ``indent1`` + parameters. + + :param str string: The text to be wrapped. + :param int max_width: The maximum number of pixels on a line before wrapping. + :param font: The font to use for measuring the text. + :type font: ~fontio.FontProtocol + :param str indent0: Additional character(s) to add to the first line. + :param str indent1: Additional character(s) to add to all other lines. + + :return: A list of the lines resulting from wrapping the + input text at ``max_width`` pixels size + :rtype: List[str] + + """ + if font is None: + + def measure(text): + return len(text) + + else: + if hasattr(font, "load_glyphs"): + font.load_glyphs(string) + + def measure(text): + total_len = 0 + for char in text: + this_glyph = font.get_glyph(ord(char)) + if this_glyph: + total_len += this_glyph.shift_x + return total_len + + lines = [] + partial = [indent0] + width = measure(indent0) + swidth = measure(" ") + firstword = True + for line_in_input in string.split("\n"): + newline = True + for index, word in enumerate(line_in_input.split(" ")): + wwidth = measure(word) + word_parts = [] + cur_part = "" + + if wwidth > max_width: + for char in word: + if newline: + extraspace = 0 + leadchar = "" + else: + extraspace = swidth + leadchar = " " + if ( + measure("".join(partial)) + + measure(cur_part) + + measure(char) + + measure("-") + + extraspace + > max_width + ): + if cur_part: + word_parts.append( + "".join(partial) + leadchar + cur_part + "-" + ) + + else: + word_parts.append("".join(partial)) + cur_part = char + partial = [indent1] + newline = True + else: + cur_part += char + if cur_part: + word_parts.append(cur_part) + for line in word_parts[:-1]: + lines.append(line) + partial.append(word_parts[-1]) + width = measure(word_parts[-1]) + if firstword: + firstword = False + else: + if firstword: + partial.append(word) + firstword = False + width += wwidth + elif width + swidth + wwidth < max_width: + if index > 0: + partial.append(" ") + partial.append(word) + width += wwidth + swidth + else: + lines.append("".join(partial)) + partial = [indent1, word] + width = measure(indent1) + wwidth + if newline: + newline = False + + lines.append("".join(partial)) + partial = [indent1] + width = measure(indent1) + + return lines + + +def wrap_text_to_lines(string: str, max_chars: int) -> List[str]: + """wrap_text_to_lines function + A helper that will return a list of lines with word-break wrapping + + :param str string: The text to be wrapped + :param int max_chars: The maximum number of characters on a line before wrapping + + :return: A list of lines where each line is separated based on the amount + of ``max_chars`` provided + :rtype: List[str] + """ + + def chunks(lst, n): + """Yield successive n-sized chunks from lst.""" + for i in range(0, len(lst), n): + yield lst[i : i + n] + + string = string.replace("\n", "").replace("\r", "") # Strip confusing newlines + words = string.split(" ") + the_lines = [] + the_line = "" + for w in words: + if len(w) > max_chars: + if the_line: # add what we had stored + the_lines.append(the_line) + parts = [] + for part in chunks(w, max_chars - 1): + parts.append("{}-".format(part)) + the_lines.extend(parts[:-1]) + the_line = parts[-1][:-1] + continue + + if len(the_line + " " + w) <= max_chars: + the_line += " " + w + elif not the_line and len(w) == max_chars: + the_lines.append(w) + else: + the_lines.append(the_line) + the_line = "" + w + if the_line: # Last line remaining + the_lines.append(the_line) + # Remove any blank lines + while not the_lines[0]: + del the_lines[0] + # Remove first space from first line: + if the_lines[0][0] == " ": + the_lines[0] = the_lines[0][1:] + return the_lines + + +class LabelBase(Group): + # pylint: disable=too-many-instance-attributes + + """Superclass that all other types of labels will extend. This contains + all of the properties and functions that work the same way in all labels. + + **Note:** This should be treated as an abstract base class. + + Subclasses should implement ``_set_text``, ``_set_font``, and ``_set_line_spacing`` to + have the correct behavior for that type of label. + + :param font: A font class that has ``get_bounding_box`` and ``get_glyph``. + Must include a capital M for measuring character size. + :type font: ~fontio.FontProtocol + :param str text: Text to display + :param int color: Color of all text in RGB hex + :param int background_color: Color of the background, use `None` for transparent + :param float line_spacing: Line spacing of text to display + :param bool background_tight: Set `True` only if you want background box to tightly + surround text. When set to 'True' Padding parameters will be ignored. + :param int padding_top: Additional pixels added to background bounding box at top + :param int padding_bottom: Additional pixels added to background bounding box at bottom + :param int padding_left: Additional pixels added to background bounding box at left + :param int padding_right: Additional pixels added to background bounding box at right + :param (float,float) anchor_point: Point that anchored_position moves relative to. + Tuple with decimal percentage of width and height. + (E.g. (0,0) is top left, (1.0, 0.5): is middle right.) + :param (int,int) anchored_position: Position relative to the anchor_point. Tuple + containing x,y pixel coordinates. + :param int scale: Integer value of the pixel scaling + :param bool base_alignment: when True allows to align text label to the baseline. + This is helpful when two or more labels need to be aligned to the same baseline + :param (int,str) tab_replacement: tuple with tab character replace information. When + (4, " ") will indicate a tab replacement of 4 spaces, defaults to 4 spaces by + tab character + :param str label_direction: string defining the label text orientation. See the + subclass documentation for the possible values. + :param bool verbose: print debugging information in some internal functions. Default to False + """ + + def __init__( + self, + font: FontProtocol, + x: int = 0, + y: int = 0, + text: str = "", + color: int = 0xFFFFFF, + background_color: int = None, + line_spacing: float = 1.25, + background_tight: bool = False, + padding_top: int = 0, + padding_bottom: int = 0, + padding_left: int = 0, + padding_right: int = 0, + anchor_point: Tuple[float, float] = None, + anchored_position: Tuple[int, int] = None, + scale: int = 1, + base_alignment: bool = False, + tab_replacement: Tuple[int, str] = (4, " "), + label_direction: str = "LTR", + verbose: bool = False, + ) -> None: + # pylint: disable=too-many-arguments, too-many-locals + + super().__init__(x=x, y=y, scale=1) + + self._font = font + self._text = text + self._palette = Palette(2) + self._color = 0xFFFFFF + self._background_color = None + self._line_spacing = line_spacing + self._background_tight = background_tight + self._padding_top = padding_top + self._padding_bottom = padding_bottom + self._padding_left = padding_left + self._padding_right = padding_right + self._anchor_point = anchor_point + self._anchored_position = anchored_position + self._base_alignment = base_alignment + self._label_direction = label_direction + self._tab_replacement = tab_replacement + self._tab_text = self._tab_replacement[1] * self._tab_replacement[0] + self._verbose = verbose + + self._ascent, self._descent = self._get_ascent_descent() + self._bounding_box = None + + self.color = color + self.background_color = background_color + + # local group will hold background and text + # the self group scale should always remain at 1, the self._local_group will + # be used to set the scale of the label + self._local_group = Group(scale=scale) + self.append(self._local_group) + + self._baseline = -1.0 + + if self._base_alignment: + self._y_offset = 0 + else: + self._y_offset = self._ascent // 2 + + def _get_ascent_descent(self) -> Tuple[int, int]: + """Private function to calculate ascent and descent font values""" + if hasattr(self.font, "ascent") and hasattr(self.font, "descent"): + return self.font.ascent, self.font.descent + + # check a few glyphs for maximum ascender and descender height + glyphs = "M j'" # choose glyphs with highest ascender and lowest + try: + self._font.load_glyphs(glyphs) + except AttributeError: + # Builtin font doesn't have or need load_glyphs + pass + # descender, will depend upon font used + ascender_max = descender_max = 0 + for char in glyphs: + this_glyph = self._font.get_glyph(ord(char)) + if this_glyph: + ascender_max = max(ascender_max, this_glyph.height + this_glyph.dy) + descender_max = max(descender_max, -this_glyph.dy) + return ascender_max, descender_max + + @property + def font(self) -> FontProtocol: + """Font to use for text display.""" + return self._font + + def _set_font(self, new_font: FontProtocol) -> None: + raise NotImplementedError("{} MUST override '_set_font'".format(type(self))) + + @font.setter + def font(self, new_font: FontProtocol) -> None: + self._set_font(new_font) + + @property + def color(self) -> int: + """Color of the text as an RGB hex number.""" + return self._color + + @color.setter + def color(self, new_color: int): + self._color = new_color + if new_color is not None: + self._palette[1] = new_color + self._palette.make_opaque(1) + else: + self._palette[1] = 0 + self._palette.make_transparent(1) + + @property + def background_color(self) -> int: + """Color of the background as an RGB hex number.""" + return self._background_color + + def _set_background_color(self, new_color): + raise NotImplementedError( + "{} MUST override '_set_background_color'".format(type(self)) + ) + + @background_color.setter + def background_color(self, new_color: int) -> None: + self._set_background_color(new_color) + + @property + def anchor_point(self) -> Tuple[float, float]: + """Point that anchored_position moves relative to. + Tuple with decimal percentage of width and height. + (E.g. (0,0) is top left, (1.0, 0.5): is middle right.)""" + return self._anchor_point + + @anchor_point.setter + def anchor_point(self, new_anchor_point: Tuple[float, float]) -> None: + if new_anchor_point[1] == self._baseline: + self._anchor_point = (new_anchor_point[0], -1.0) + else: + self._anchor_point = new_anchor_point + + # update the anchored_position using setter + self.anchored_position = self._anchored_position + + @property + def anchored_position(self) -> Tuple[int, int]: + """Position relative to the anchor_point. Tuple containing x,y + pixel coordinates.""" + return self._anchored_position + + @anchored_position.setter + def anchored_position(self, new_position: Tuple[int, int]) -> None: + self._anchored_position = new_position + # Calculate (x,y) position + if (self._anchor_point is not None) and (self._anchored_position is not None): + self.x = int( + new_position[0] + - (self._bounding_box[0] * self.scale) + - round(self._anchor_point[0] * (self._bounding_box[2] * self.scale)) + ) + if self._anchor_point[1] == self._baseline: + self.y = int(new_position[1] - (self._y_offset * self.scale)) + else: + self.y = int( + new_position[1] + - (self._bounding_box[1] * self.scale) + - round(self._anchor_point[1] * self._bounding_box[3] * self.scale) + ) + + @property + def scale(self) -> int: + """Set the scaling of the label, in integer values""" + return self._local_group.scale + + @scale.setter + def scale(self, new_scale: int) -> None: + self._local_group.scale = new_scale + self.anchored_position = self._anchored_position # update the anchored_position + + def _set_text(self, new_text: str, scale: int) -> None: + raise NotImplementedError("{} MUST override '_set_text'".format(type(self))) + + @property + def text(self) -> str: + """Text to be displayed.""" + return self._text + + @text.setter # Cannot set color or background color with text setter, use separate setter + def text(self, new_text: str) -> None: + self._set_text(new_text, self.scale) + + @property + def bounding_box(self) -> Tuple[int, int]: + """An (x, y, w, h) tuple that completely covers all glyphs. The + first two numbers are offset from the x, y origin of this group""" + return tuple(self._bounding_box) + + @property + def height(self) -> int: + """The height of the label determined from the bounding box.""" + return self._bounding_box[3] + + @property + def width(self) -> int: + """The width of the label determined from the bounding box.""" + return self._bounding_box[2] + + @property + def line_spacing(self) -> float: + """The amount of space between lines of text, in multiples of the font's + bounding-box height. (E.g. 1.0 is the bounding-box height)""" + return self._line_spacing + + def _set_line_spacing(self, new_line_spacing: float) -> None: + raise NotImplementedError( + "{} MUST override '_set_line_spacing'".format(type(self)) + ) + + @line_spacing.setter + def line_spacing(self, new_line_spacing: float) -> None: + self._set_line_spacing(new_line_spacing) + + @property + def label_direction(self) -> str: + """Set the text direction of the label""" + return self._label_direction + + def _set_label_direction(self, new_label_direction: str) -> None: + raise NotImplementedError( + "{} MUST override '_set_label_direction'".format(type(self)) + ) + + def _get_valid_label_directions(self) -> Tuple[str, ...]: + raise NotImplementedError( + "{} MUST override '_get_valid_label_direction'".format(type(self)) + ) + + @label_direction.setter + def label_direction(self, new_label_direction: str) -> None: + """Set the text direction of the label""" + if new_label_direction not in self._get_valid_label_directions(): + raise RuntimeError("Please provide a valid text direction") + self._set_label_direction(new_label_direction) + + def _replace_tabs(self, text: str) -> str: + return text if text.find("\t") < 0 else self._tab_text.join(text.split("\t")) diff --git a/src/lib/adafruit_display_text/bitmap_label.py b/src/lib/adafruit_display_text/bitmap_label.py new file mode 100644 index 0000000..514ae69 --- /dev/null +++ b/src/lib/adafruit_display_text/bitmap_label.py @@ -0,0 +1,597 @@ +# SPDX-FileCopyrightText: 2020 Kevin Matocha +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_display_text.bitmap_label` +================================================================================ + +Text graphics handling for CircuitPython, including text boxes + + +* Author(s): Kevin Matocha + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +""" + +__version__ = "3.2.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" + +import displayio +from adafruit_display_text import LabelBase + +try: + import bitmaptools +except ImportError: + # We have a slower fallback for bitmaptools + pass + +try: + from typing import Optional, Tuple + from fontio import FontProtocol +except ImportError: + pass + + +# pylint: disable=too-many-instance-attributes +class Label(LabelBase): + """A label displaying a string of text that is stored in a bitmap. + Note: This ``bitmap_label.py`` library utilizes a :py:class:`~displayio.Bitmap` + to display the text. This method is memory-conserving relative to ``label.py``. + + For further reduction in memory usage, set ``save_text=False`` (text string will not + be stored and ``line_spacing`` and ``font`` are immutable with ``save_text`` + set to ``False``). + + The origin point set by ``x`` and ``y`` + properties will be the left edge of the bounding box, and in the center of a M + glyph (if its one line), or the (number of lines * linespacing + M)/2. That is, + it will try to have it be center-left as close as possible. + + :param font: A font class that has ``get_bounding_box`` and ``get_glyph``. + Must include a capital M for measuring character size. + :type font: ~fontio.FontProtocol + :param str text: Text to display + :param int|Tuple(int, int, int) color: Color of all text in HEX or RGB + :param int|Tuple(int, int, int)|None background_color: Color of the background, use `None` + for transparent + :param float line_spacing: Line spacing of text to display + :param bool background_tight: Set `True` only if you want background box to tightly + surround text. When set to 'True' Padding parameters will be ignored. + :param int padding_top: Additional pixels added to background bounding box at top + :param int padding_bottom: Additional pixels added to background bounding box at bottom + :param int padding_left: Additional pixels added to background bounding box at left + :param int padding_right: Additional pixels added to background bounding box at right + :param Tuple(float, float) anchor_point: Point that anchored_position moves relative to. + Tuple with decimal percentage of width and height. + (E.g. (0,0) is top left, (1.0, 0.5): is middle right.) + :param Tuple(int, int) anchored_position: Position relative to the anchor_point. Tuple + containing x,y pixel coordinates. + :param int scale: Integer value of the pixel scaling + :param bool save_text: Set True to save the text string as a constant in the + label structure. Set False to reduce memory use. + :param bool base_alignment: when True allows to align text label to the baseline. + This is helpful when two or more labels need to be aligned to the same baseline + :param Tuple(int, str) tab_replacement: tuple with tab character replace information. When + (4, " ") will indicate a tab replacement of 4 spaces, defaults to 4 spaces by + tab character + :param str label_direction: string defining the label text orientation. There are 5 + configurations possibles ``LTR``-Left-To-Right ``RTL``-Right-To-Left + ``UPD``-Upside Down ``UPR``-Upwards ``DWR``-Downwards. It defaults to ``LTR`` + :param bool verbose: print debugging information in some internal functions. Default to False + + """ + + # This maps label_direction to TileGrid's transpose_xy, flip_x, flip_y + _DIR_MAP = { + "UPR": (True, True, False), + "DWR": (True, False, True), + "UPD": (False, True, True), + "LTR": (False, False, False), + "RTL": (False, False, False), + } + + def __init__(self, font: FontProtocol, save_text: bool = True, **kwargs) -> None: + self._bitmap = None + self._tilegrid = None + self._prev_label_direction = None + + super().__init__(font, **kwargs) + + self._save_text = save_text + self._text = self._replace_tabs(self._text) + + # call the text updater with all the arguments. + self._reset_text( + font=font, + text=self._text, + line_spacing=self._line_spacing, + scale=self.scale, + ) + + def _reset_text( + self, + font: Optional[FontProtocol] = None, + text: Optional[str] = None, + line_spacing: Optional[float] = None, + scale: Optional[int] = None, + ) -> None: + # pylint: disable=too-many-branches, too-many-statements, too-many-locals + + # Store all the instance variables + if font is not None: + self._font = font + if line_spacing is not None: + self._line_spacing = line_spacing + + # if text is not provided as a parameter (text is None), use the previous value. + if (text is None) and self._save_text: + text = self._text + + if self._save_text: # text string will be saved + self._text = self._replace_tabs(text) + else: + self._text = None # save a None value since text string is not saved + + # Check for empty string + if (text == "") or ( + text is None + ): # If empty string, just create a zero-sized bounding box and that's it. + self._bounding_box = ( + 0, + 0, + 0, # zero width with text == "" + 0, # zero height with text == "" + ) + # Clear out any items in the self._local_group Group, in case this is an + # update to the bitmap_label + for _ in self._local_group: + self._local_group.pop(0) + + # Free the bitmap and tilegrid since they are removed + self._bitmap = None + self._tilegrid = None + + else: # The text string is not empty, so create the Bitmap and TileGrid and + # append to the self Group + + # Calculate the text bounding box + + # Calculate both "tight" and "loose" bounding box dimensions to match label for + # anchor_position calculations + ( + box_x, + tight_box_y, + x_offset, + tight_y_offset, + loose_box_y, + loose_y_offset, + ) = self._text_bounding_box( + text, + self._font, + ) # calculate the box size for a tight and loose backgrounds + + if self._background_tight: + box_y = tight_box_y + y_offset = tight_y_offset + self._padding_left = 0 + self._padding_right = 0 + self._padding_top = 0 + self._padding_bottom = 0 + + else: # calculate the box size for a loose background + box_y = loose_box_y + y_offset = loose_y_offset + + # Calculate the background size including padding + tight_box_x = box_x + box_x = box_x + self._padding_left + self._padding_right + box_y = box_y + self._padding_top + self._padding_bottom + + # Create the Bitmap unless it can be reused + new_bitmap = None + if ( + self._bitmap is None + or self._bitmap.width != box_x + or self._bitmap.height != box_y + ): + new_bitmap = displayio.Bitmap(box_x, box_y, len(self._palette)) + self._bitmap = new_bitmap + else: + self._bitmap.fill(0) + + # Place the text into the Bitmap + self._place_text( + self._bitmap, + text if self._label_direction != "RTL" else "".join(reversed(text)), + self._font, + self._padding_left - x_offset, + self._padding_top + y_offset, + ) + + if self._base_alignment: + label_position_yoffset = 0 + else: + label_position_yoffset = self._ascent // 2 + + # Create the TileGrid if not created bitmap unchanged + if self._tilegrid is None or new_bitmap: + self._tilegrid = displayio.TileGrid( + self._bitmap, + pixel_shader=self._palette, + width=1, + height=1, + tile_width=box_x, + tile_height=box_y, + default_tile=0, + x=-self._padding_left + x_offset, + y=label_position_yoffset - y_offset - self._padding_top, + ) + # Clear out any items in the local_group Group, in case this is an update to + # the bitmap_label + for _ in self._local_group: + self._local_group.pop(0) + self._local_group.append( + self._tilegrid + ) # add the bitmap's tilegrid to the group + + # Set TileGrid properties based on label_direction + if self._label_direction != self._prev_label_direction: + tg1 = self._tilegrid + tg1.transpose_xy, tg1.flip_x, tg1.flip_y = self._DIR_MAP[ + self._label_direction + ] + + # Update bounding_box values. Note: To be consistent with label.py, + # this is the bounding box for the text only, not including the background. + if self._label_direction in ("UPR", "DWR"): + if self._label_direction == "UPR": + top = self._padding_right + left = self._padding_top + if self._label_direction == "DWR": + top = self._padding_left + left = self._padding_bottom + self._bounding_box = ( + self._tilegrid.x + left, + self._tilegrid.y + top, + tight_box_y, + tight_box_x, + ) + else: + self._bounding_box = ( + self._tilegrid.x + self._padding_left, + self._tilegrid.y + self._padding_top, + tight_box_x, + tight_box_y, + ) + + if ( + scale is not None + ): # Scale will be defined in local_group (Note: self should have scale=1) + self.scale = scale # call the setter + + # set the anchored_position with setter after bitmap is created, sets the + # x,y positions of the label + self.anchored_position = self._anchored_position + + @staticmethod + def _line_spacing_ypixels(font: FontProtocol, line_spacing: float) -> int: + # Note: Scaling is provided at the Group level + return_value = int(line_spacing * font.get_bounding_box()[1]) + return return_value + + def _text_bounding_box( + self, text: str, font: FontProtocol + ) -> Tuple[int, int, int, int, int, int]: + # pylint: disable=too-many-locals,too-many-branches + + bbox = font.get_bounding_box() + if len(bbox) == 4: + ascender_max, descender_max = bbox[1], -bbox[3] + else: + ascender_max, descender_max = self._ascent, self._descent + + lines = 1 + + # starting x and y position (left margin) + xposition = x_start = yposition = y_start = 0 + + left = None + right = x_start + top = bottom = y_start + + y_offset_tight = self._ascent // 2 + + newlines = 0 + line_spacing = self._line_spacing + + for char in text: + if char == "\n": # newline + newlines += 1 + + else: + my_glyph = font.get_glyph(ord(char)) + + if my_glyph is None: # Error checking: no glyph found + print("Glyph not found: {}".format(repr(char))) + else: + if newlines: + xposition = x_start # reset to left column + yposition += ( + self._line_spacing_ypixels(font, line_spacing) * newlines + ) # Add the newline(s) + lines += newlines + newlines = 0 + if xposition == x_start: + if left is None: + left = 0 + else: + left = min(left, my_glyph.dx) + xright = xposition + my_glyph.width + my_glyph.dx + xposition += my_glyph.shift_x + + right = max(right, xposition, xright) + + if yposition == y_start: # first line, find the Ascender height + top = min(top, -my_glyph.height - my_glyph.dy + y_offset_tight) + bottom = max(bottom, yposition - my_glyph.dy + y_offset_tight) + + if left is None: + left = 0 + + final_box_width = right - left + + final_box_height_tight = bottom - top + final_y_offset_tight = -top + y_offset_tight + + final_box_height_loose = (lines - 1) * self._line_spacing_ypixels( + font, line_spacing + ) + (ascender_max + descender_max) + final_y_offset_loose = ascender_max + + # return (final_box_width, final_box_height, left, final_y_offset) + + return ( + final_box_width, + final_box_height_tight, + left, + final_y_offset_tight, + final_box_height_loose, + final_y_offset_loose, + ) + + # pylint: disable = too-many-branches + def _place_text( + self, + bitmap: displayio.Bitmap, + text: str, + font: FontProtocol, + xposition: int, + yposition: int, + skip_index: int = 0, # set to None to write all pixels, other wise skip this palette index + # when copying glyph bitmaps (this is important for slanted text + # where rectangular glyph boxes overlap) + ) -> Tuple[int, int, int, int]: + # pylint: disable=too-many-arguments, too-many-locals + + # placeText - Writes text into a bitmap at the specified location. + # + # Note: scale is pushed up to Group level + + x_start = xposition # starting x position (left margin) + y_start = yposition + + left = None + right = x_start + top = bottom = y_start + line_spacing = self._line_spacing + + for char in text: + if char == "\n": # newline + xposition = x_start # reset to left column + yposition = yposition + self._line_spacing_ypixels( + font, line_spacing + ) # Add a newline + + else: + my_glyph = font.get_glyph(ord(char)) + + if my_glyph is None: # Error checking: no glyph found + print("Glyph not found: {}".format(repr(char))) + else: + if xposition == x_start: + if left is None: + left = 0 + else: + left = min(left, my_glyph.dx) + + right = max( + right, + xposition + my_glyph.shift_x, + xposition + my_glyph.width + my_glyph.dx, + ) + if yposition == y_start: # first line, find the Ascender height + top = min(top, -my_glyph.height - my_glyph.dy) + bottom = max(bottom, yposition - my_glyph.dy) + + glyph_offset_x = ( + my_glyph.tile_index * my_glyph.width + ) # for type BuiltinFont, this creates the x-offset in the glyph bitmap. + # for BDF loaded fonts, this should equal 0 + + y_blit_target = yposition - my_glyph.height - my_glyph.dy + + # Clip glyph y-direction if outside the font ascent/descent metrics. + # Note: bitmap.blit will automatically clip the bottom of the glyph. + y_clip = 0 + if y_blit_target < 0: + y_clip = -y_blit_target # clip this amount from top of bitmap + y_blit_target = 0 # draw the clipped bitmap at y=0 + if self._verbose: + print( + 'Warning: Glyph clipped, exceeds Ascent property: "{}"'.format( + char + ) + ) + + if (y_blit_target + my_glyph.height) > bitmap.height: + if self._verbose: + print( + 'Warning: Glyph clipped, exceeds descent property: "{}"'.format( + char + ) + ) + + self._blit( + bitmap, + max(xposition + my_glyph.dx, 0), + y_blit_target, + my_glyph.bitmap, + x_1=glyph_offset_x, + y_1=y_clip, + x_2=glyph_offset_x + my_glyph.width, + y_2=my_glyph.height, + skip_index=skip_index, # do not copy over any 0 background pixels + ) + + xposition = xposition + my_glyph.shift_x + + # bounding_box + return left, top, right - left, bottom - top + + def _blit( + self, + bitmap: displayio.Bitmap, # target bitmap + x: int, # target x upper left corner + y: int, # target y upper left corner + source_bitmap: displayio.Bitmap, # source bitmap + x_1: int = 0, # source x start + y_1: int = 0, # source y start + x_2: int = None, # source x end + y_2: int = None, # source y end + skip_index: int = None, # palette index that will not be copied + # (for example: the background color of a glyph) + ) -> None: + # pylint: disable=no-self-use, too-many-arguments + + if hasattr(bitmap, "blit"): # if bitmap has a built-in blit function, call it + # this function should perform its own input checks + bitmap.blit( + x, + y, + source_bitmap, + x1=x_1, + y1=y_1, + x2=x_2, + y2=y_2, + skip_index=skip_index, + ) + elif hasattr(bitmaptools, "blit"): + bitmaptools.blit( + bitmap, + source_bitmap, + x, + y, + x1=x_1, + y1=y_1, + x2=x_2, + y2=y_2, + skip_source_index=skip_index, + ) + + else: # perform pixel by pixel copy of the bitmap + # Perform input checks + + if x_2 is None: + x_2 = source_bitmap.width + if y_2 is None: + y_2 = source_bitmap.height + + # Rearrange so that x_1 < x_2 and y1 < y2 + if x_1 > x_2: + x_1, x_2 = x_2, x_1 + if y_1 > y_2: + y_1, y_2 = y_2, y_1 + + # Ensure that x2 and y2 are within source bitmap size + x_2 = min(x_2, source_bitmap.width) + y_2 = min(y_2, source_bitmap.height) + + for y_count in range(y_2 - y_1): + for x_count in range(x_2 - x_1): + x_placement = x + x_count + y_placement = y + y_count + + if (bitmap.width > x_placement >= 0) and ( + bitmap.height > y_placement >= 0 + ): # ensure placement is within target bitmap + # get the palette index from the source bitmap + this_pixel_color = source_bitmap[ + y_1 + + ( + y_count * source_bitmap.width + ) # Direct index into a bitmap array is speedier than [x,y] tuple + + x_1 + + x_count + ] + + if (skip_index is None) or (this_pixel_color != skip_index): + bitmap[ # Direct index into a bitmap array is speedier than [x,y] tuple + y_placement * bitmap.width + x_placement + ] = this_pixel_color + elif y_placement > bitmap.height: + break + + def _set_line_spacing(self, new_line_spacing: float) -> None: + if self._save_text: + self._reset_text(line_spacing=new_line_spacing, scale=self.scale) + else: + raise RuntimeError("line_spacing is immutable when save_text is False") + + def _set_font(self, new_font: FontProtocol) -> None: + self._font = new_font + if self._save_text: + self._reset_text(font=new_font, scale=self.scale) + else: + raise RuntimeError("font is immutable when save_text is False") + + def _set_text(self, new_text: str, scale: int) -> None: + self._reset_text(text=self._replace_tabs(new_text), scale=self.scale) + + def _set_background_color(self, new_color: Optional[int]): + self._background_color = new_color + if new_color is not None: + self._palette[0] = new_color + self._palette.make_opaque(0) + else: + self._palette[0] = 0 + self._palette.make_transparent(0) + + def _set_label_direction(self, new_label_direction: str) -> None: + # Only make changes if new direction is different + # to prevent errors in the _reset_text() direction checks + if self._label_direction != new_label_direction: + self._prev_label_direction = self._label_direction + self._label_direction = new_label_direction + self._reset_text(text=str(self._text)) # Force a recalculation + + def _get_valid_label_directions(self) -> Tuple[str, ...]: + return "LTR", "RTL", "UPD", "UPR", "DWR" + + @property + def bitmap(self) -> displayio.Bitmap: + """ + The Bitmap object that the text and background are drawn into. + + :rtype: displayio.Bitmap + """ + return self._bitmap diff --git a/src/lib/adafruit_display_text/label.py b/src/lib/adafruit_display_text/label.py new file mode 100644 index 0000000..7dd64a2 --- /dev/null +++ b/src/lib/adafruit_display_text/label.py @@ -0,0 +1,447 @@ +# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_display_text.label` +==================================================== + +Displays text labels using CircuitPython's displayio. + +* Author(s): Scott Shawcroft + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +""" + +__version__ = "3.2.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" + + +from displayio import Bitmap, Palette, TileGrid +from adafruit_display_text import LabelBase + +try: + from typing import Optional, Tuple + from fontio import FontProtocol +except ImportError: + pass + + +class Label(LabelBase): + # pylint: disable=too-many-instance-attributes + + """A label displaying a string of text. The origin point set by ``x`` and ``y`` + properties will be the left edge of the bounding box, and in the center of a M + glyph (if its one line), or the (number of lines * linespacing + M)/2. That is, + it will try to have it be center-left as close as possible. + + :param font: A font class that has ``get_bounding_box`` and ``get_glyph``. + Must include a capital M for measuring character size. + :type font: ~fontio.FontProtocol + :param str text: Text to display + :param int|Tuple(int, int, int) color: Color of all text in HEX or RGB + :param int|Tuple(int, int, int)|None background_color: Color of the background, use `None` + for transparent + :param float line_spacing: Line spacing of text to display + :param bool background_tight: Set `True` only if you want background box to tightly + surround text. When set to 'True' Padding parameters will be ignored. + :param int padding_top: Additional pixels added to background bounding box at top. + This parameter could be negative indicating additional pixels subtracted from the + background bounding box. + :param int padding_bottom: Additional pixels added to background bounding box at bottom. + This parameter could be negative indicating additional pixels subtracted from the + background bounding box. + :param int padding_left: Additional pixels added to background bounding box at left. + This parameter could be negative indicating additional pixels subtracted from the + background bounding box. + :param int padding_right: Additional pixels added to background bounding box at right. + This parameter could be negative indicating additional pixels subtracted from the + background bounding box. + :param Tuple(float, float) anchor_point: Point that anchored_position moves relative to. + Tuple with decimal percentage of width and height. + (E.g. (0,0) is top left, (1.0, 0.5): is middle right.) + :param Tuple(int, int) anchored_position: Position relative to the anchor_point. Tuple + containing x,y pixel coordinates. + :param int scale: Integer value of the pixel scaling + :param bool base_alignment: when True allows to align text label to the baseline. + This is helpful when two or more labels need to be aligned to the same baseline + :param Tuple(int, str) tab_replacement: tuple with tab character replace information. When + (4, " ") will indicate a tab replacement of 4 spaces, defaults to 4 spaces by + tab character + :param str label_direction: string defining the label text orientation. There are 5 + configurations possibles ``LTR``-Left-To-Right ``RTL``-Right-To-Left + ``TTB``-Top-To-Bottom ``UPR``-Upwards ``DWR``-Downwards. It defaults to ``LTR``""" + + def __init__(self, font: FontProtocol, **kwargs) -> None: + self._background_palette = Palette(1) + self._added_background_tilegrid = False + + super().__init__(font, **kwargs) + + text = self._replace_tabs(self._text) + + self._width = len(text) + self._height = self._font.get_bounding_box()[1] + + # Create the two-color text palette + self._palette[0] = 0 + self._palette.make_transparent(0) + + if text is not None: + self._reset_text(str(text)) + + # pylint: disable=too-many-branches + def _create_background_box(self, lines: int, y_offset: int) -> TileGrid: + """Private Class function to create a background_box + :param lines: int number of lines + :param y_offset: int y pixel bottom coordinate for the background_box""" + + left = self._bounding_box[0] + if self._background_tight: # draw a tight bounding box + box_width = self._bounding_box[2] + box_height = self._bounding_box[3] + x_box_offset = 0 + y_box_offset = self._bounding_box[1] + + else: # draw a "loose" bounding box to include any ascenders/descenders. + ascent, descent = self._ascent, self._descent + + if self._label_direction in ("DWR", "UPR"): + box_height = ( + self._bounding_box[3] + self._padding_right + self._padding_left + ) + x_box_offset = -self._padding_left + box_width = ( + (ascent + descent) + + int((lines - 1) * self._width * self._line_spacing) + + self._padding_top + + self._padding_bottom + ) + elif self._label_direction == "TTB": + box_height = ( + self._bounding_box[3] + self._padding_top + self._padding_bottom + ) + x_box_offset = -self._padding_left + box_width = ( + (ascent + descent) + + int((lines - 1) * self._height * self._line_spacing) + + self._padding_right + + self._padding_left + ) + else: + box_width = ( + self._bounding_box[2] + self._padding_left + self._padding_right + ) + x_box_offset = -self._padding_left + box_height = ( + (ascent + descent) + + int((lines - 1) * self._height * self._line_spacing) + + self._padding_top + + self._padding_bottom + ) + + if self._label_direction == "DWR": + padding_to_use = self._padding_bottom + elif self._label_direction == "TTB": + padding_to_use = self._padding_top + y_offset = 0 + ascent = 0 + else: + padding_to_use = self._padding_top + + if self._base_alignment: + y_box_offset = -ascent - padding_to_use + else: + y_box_offset = -ascent + y_offset - padding_to_use + + box_width = max(0, box_width) # remove any negative values + box_height = max(0, box_height) # remove any negative values + + if self._label_direction == "UPR": + movx = y_box_offset + movy = -box_height - x_box_offset + elif self._label_direction == "DWR": + movx = y_box_offset + movy = x_box_offset + elif self._label_direction == "TTB": + movx = x_box_offset + movy = y_box_offset + else: + movx = left + x_box_offset + movy = y_box_offset + + background_bitmap = Bitmap(box_width, box_height, 1) + tile_grid = TileGrid( + background_bitmap, + pixel_shader=self._background_palette, + x=movx, + y=movy, + ) + + return tile_grid + + # pylint: enable=too-many-branches + def _set_background_color(self, new_color: Optional[int]) -> None: + """Private class function that allows updating the font box background color + + :param int new_color: Color as an RGB hex number, setting to None makes it transparent + """ + + if new_color is None: + self._background_palette.make_transparent(0) + if self._added_background_tilegrid: + self._local_group.pop(0) + self._added_background_tilegrid = False + else: + self._background_palette.make_opaque(0) + self._background_palette[0] = new_color + self._background_color = new_color + + lines = self._text.rstrip("\n").count("\n") + 1 + y_offset = self._ascent // 2 + + if self._bounding_box is None: + # Still in initialization + return + + if not self._added_background_tilegrid: # no bitmap is in the self Group + # add bitmap if text is present and bitmap sizes > 0 pixels + if ( + (len(self._text) > 0) + and ( + self._bounding_box[2] + self._padding_left + self._padding_right > 0 + ) + and ( + self._bounding_box[3] + self._padding_top + self._padding_bottom > 0 + ) + ): + self._local_group.insert( + 0, self._create_background_box(lines, y_offset) + ) + self._added_background_tilegrid = True + + else: # a bitmap is present in the self Group + # update bitmap if text is present and bitmap sizes > 0 pixels + if ( + (len(self._text) > 0) + and ( + self._bounding_box[2] + self._padding_left + self._padding_right > 0 + ) + and ( + self._bounding_box[3] + self._padding_top + self._padding_bottom > 0 + ) + ): + self._local_group[0] = self._create_background_box( + lines, self._y_offset + ) + else: # delete the existing bitmap + self._local_group.pop(0) + self._added_background_tilegrid = False + + def _update_text(self, new_text: str) -> None: + # pylint: disable=too-many-branches,too-many-statements + + x = 0 + y = 0 + if self._added_background_tilegrid: + i = 1 + else: + i = 0 + tilegrid_count = i + if self._base_alignment: + self._y_offset = 0 + else: + self._y_offset = self._ascent // 2 + + if self._label_direction == "RTL": + left = top = bottom = 0 + right = None + elif self._label_direction == "LTR": + right = top = bottom = 0 + left = None + else: + top = right = left = 0 + bottom = 0 + + for character in new_text: + if character == "\n": + y += int(self._height * self._line_spacing) + x = 0 + continue + glyph = self._font.get_glyph(ord(character)) + if not glyph: + continue + + position_x, position_y = 0, 0 + + if self._label_direction in ("LTR", "RTL"): + bottom = max(bottom, y - glyph.dy + self._y_offset) + if y == 0: # first line, find the Ascender height + top = min(top, -glyph.height - glyph.dy + self._y_offset) + position_y = y - glyph.height - glyph.dy + self._y_offset + + if self._label_direction == "LTR": + right = max(right, x + glyph.shift_x, x + glyph.width + glyph.dx) + if x == 0: + if left is None: + left = 0 + else: + left = min(left, glyph.dx) + position_x = x + glyph.dx + else: + left = max( + left, abs(x) + glyph.shift_x, abs(x) + glyph.width + glyph.dx + ) + if x == 0: + if right is None: + right = 0 + else: + right = max(right, glyph.dx) + position_x = x - glyph.width + + elif self._label_direction == "TTB": + if x == 0: + if left is None: + left = 0 + else: + left = min(left, glyph.dx) + if y == 0: + top = min(top, -glyph.dy) + + bottom = max(bottom, y + glyph.height, y + glyph.height + glyph.dy) + right = max( + right, x + glyph.width + glyph.dx, x + glyph.shift_x + glyph.dx + ) + position_y = y + glyph.dy + position_x = x - glyph.width // 2 + self._y_offset + + elif self._label_direction == "UPR": + if x == 0: + if bottom is None: + bottom = -glyph.dx + + if y == 0: # first line, find the Ascender height + bottom = min(bottom, -glyph.dy) + left = min(left, x - glyph.height + self._y_offset) + top = min(top, y - glyph.width - glyph.dx, y - glyph.shift_x) + right = max(right, x + glyph.height, x + glyph.height - glyph.dy) + position_y = y - glyph.width - glyph.dx + position_x = x - glyph.height - glyph.dy + self._y_offset + + elif self._label_direction == "DWR": + if y == 0: + if top is None: + top = -glyph.dx + top = min(top, -glyph.dx) + if x == 0: + left = min(left, -glyph.dy) + left = min(left, x, x - glyph.dy - self._y_offset) + bottom = max(bottom, y + glyph.width + glyph.dx, y + glyph.shift_x) + right = max(right, x + glyph.height) + position_y = y + glyph.dx + position_x = x + glyph.dy - self._y_offset + + if glyph.width > 0 and glyph.height > 0: + face = TileGrid( + glyph.bitmap, + pixel_shader=self._palette, + default_tile=glyph.tile_index, + tile_width=glyph.width, + tile_height=glyph.height, + x=position_x, + y=position_y, + ) + + if self._label_direction == "UPR": + face.transpose_xy = True + face.flip_x = True + if self._label_direction == "DWR": + face.transpose_xy = True + face.flip_y = True + + if tilegrid_count < len(self._local_group): + self._local_group[tilegrid_count] = face + else: + self._local_group.append(face) + tilegrid_count += 1 + + if self._label_direction == "RTL": + x = x - glyph.shift_x + if self._label_direction == "TTB": + if glyph.height < 2: + y = y + glyph.shift_x + else: + y = y + glyph.height + 1 + if self._label_direction == "UPR": + y = y - glyph.shift_x + if self._label_direction == "DWR": + y = y + glyph.shift_x + if self._label_direction == "LTR": + x = x + glyph.shift_x + + i += 1 + + if self._label_direction == "LTR" and left is None: + left = 0 + if self._label_direction == "RTL" and right is None: + right = 0 + if self._label_direction == "TTB" and top is None: + top = 0 + + while len(self._local_group) > tilegrid_count: # i: + self._local_group.pop() + + if self._label_direction == "RTL": + # pylint: disable=invalid-unary-operand-type + # type-checkers think left can be None + self._bounding_box = (-left, top, left - right, bottom - top) + if self._label_direction == "TTB": + self._bounding_box = (left, top, right - left, bottom - top) + if self._label_direction == "UPR": + self._bounding_box = (left, top, right, bottom - top) + if self._label_direction == "DWR": + self._bounding_box = (left, top, right, bottom - top) + if self._label_direction == "LTR": + self._bounding_box = (left, top, right - left, bottom - top) + + self._text = new_text + + if self._background_color is not None: + self._set_background_color(self._background_color) + + def _reset_text(self, new_text: str) -> None: + current_anchored_position = self.anchored_position + self._update_text(str(self._replace_tabs(new_text))) + self.anchored_position = current_anchored_position + + def _set_font(self, new_font: FontProtocol) -> None: + old_text = self._text + current_anchored_position = self.anchored_position + self._text = "" + self._font = new_font + self._height = self._font.get_bounding_box()[1] + self._update_text(str(old_text)) + self.anchored_position = current_anchored_position + + def _set_line_spacing(self, new_line_spacing: float) -> None: + self._line_spacing = new_line_spacing + self.text = self._text # redraw the box + + def _set_text(self, new_text: str, scale: int) -> None: + self._reset_text(new_text) + + def _set_label_direction(self, new_label_direction: str) -> None: + self._label_direction = new_label_direction + self._update_text(str(self._text)) + + def _get_valid_label_directions(self) -> Tuple[str, ...]: + return "LTR", "RTL", "UPR", "DWR", "TTB" diff --git a/src/lib/adafruit_display_text/outlined_label.py b/src/lib/adafruit_display_text/outlined_label.py new file mode 100644 index 0000000..050ceb9 --- /dev/null +++ b/src/lib/adafruit_display_text/outlined_label.py @@ -0,0 +1,188 @@ +# SPDX-FileCopyrightText: 2023 Tim C +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_display_text.outlined_label` +==================================================== + +Subclass of BitmapLabel that adds outline color and stroke size +functionalities. + +* Author(s): Tim Cocks + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +""" +__version__ = "3.2.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" + +import bitmaptools +from displayio import Palette, Bitmap +from adafruit_display_text import bitmap_label + +try: + from typing import Optional, Tuple, Union + from fontio import FontProtocol +except ImportError: + pass + + +class OutlinedLabel(bitmap_label.Label): + """ + OutlinedLabel - A BitmapLabel subclass that includes arguments and properties for specifying + outline_size and outline_color to get drawn as a stroke around the text. + + :param Union[Tuple, int] outline_color: The color of the outline stroke as RGB tuple, or hex. + :param int outline_size: The size in pixels of the outline stroke. + + """ + + # pylint: disable=too-many-arguments + def __init__( + self, + font, + outline_color: Union[int, Tuple] = 0x999999, + outline_size: int = 1, + padding_top: Optional[int] = None, + padding_bottom: Optional[int] = None, + padding_left: Optional[int] = None, + padding_right: Optional[int] = None, + **kwargs + ): + if padding_top is None: + padding_top = outline_size + 0 + if padding_bottom is None: + padding_bottom = outline_size + 2 + if padding_left is None: + padding_left = outline_size + 0 + if padding_right is None: + padding_right = outline_size + 0 + + super().__init__( + font, + padding_top=padding_top, + padding_bottom=padding_bottom, + padding_left=padding_left, + padding_right=padding_right, + **kwargs + ) + + _background_color = self._palette[0] + _foreground_color = self._palette[1] + _background_is_transparent = self._palette.is_transparent(0) + self._palette = Palette(3) + self._palette[0] = _background_color + self._palette[1] = _foreground_color + self._palette[2] = outline_color + if _background_is_transparent: + self._palette.make_transparent(0) + + self._outline_size = outline_size + self._stamp_source = Bitmap((outline_size * 2) + 1, (outline_size * 2) + 1, 3) + self._stamp_source.fill(2) + + self._bitmap = None + + self._reset_text( + font=font, + text=self._text, + line_spacing=self._line_spacing, + scale=self.scale, + ) + + def _add_outline(self): + """ + Blit the outline into the labels Bitmap. We will stamp self._stamp_source for each + pixel of the foreground color but skip the foreground color when we blit. + :return: None + """ + if hasattr(self, "_stamp_source"): + for y in range(self.bitmap.height): + for x in range(self.bitmap.width): + if self.bitmap[x, y] == 1: + try: + bitmaptools.blit( + self.bitmap, + self._stamp_source, + x - self._outline_size, + y - self._outline_size, + skip_dest_index=1, + ) + except ValueError as value_error: + raise ValueError( + "Padding must be big enough to fit outline_size " + "all the way around the text. " + "Try using either larger padding sizes, or smaller outline_size." + ) from value_error + + def _place_text( + self, + bitmap: Bitmap, + text: str, + font: FontProtocol, + xposition: int, + yposition: int, + skip_index: int = 0, # set to None to write all pixels, other wise skip this palette index + # when copying glyph bitmaps (this is important for slanted text + # where rectangular glyph boxes overlap) + ) -> Tuple[int, int, int, int]: + """ + Copy the glpyphs that represent the value of the string into the labels Bitmap. + :param bitmap: The bitmap to place text into + :param text: The text to render + :param font: The font to render the text in + :param xposition: x location of the starting point within the bitmap + :param yposition: y location of the starting point within the bitmap + :param skip_index: Color index to skip during rendering instead of covering up + :return Tuple bounding_box: tuple with x, y, width, height values of the bitmap + """ + parent_result = super()._place_text( + bitmap, text, font, xposition, yposition, skip_index=skip_index + ) + + self._add_outline() + + return parent_result + + @property + def outline_color(self): + """Color of the outline to draw around the text.""" + return self._palette[2] + + @outline_color.setter + def outline_color(self, new_outline_color): + self._palette[2] = new_outline_color + + @property + def outline_size(self): + """Stroke size of the outline to draw around the text.""" + return self._outline_size + + @outline_size.setter + def outline_size(self, new_outline_size): + self._outline_size = new_outline_size + + self._padding_top = new_outline_size + 0 + self._padding_bottom = new_outline_size + 2 + self._padding_left = new_outline_size + 0 + self._padding_right = new_outline_size + 0 + + self._stamp_source = Bitmap( + (new_outline_size * 2) + 1, (new_outline_size * 2) + 1, 3 + ) + self._stamp_source.fill(2) + self._reset_text( + font=self._font, + text=self._text, + line_spacing=self._line_spacing, + scale=self.scale, + ) diff --git a/src/lib/adafruit_display_text/scrolling_label.py b/src/lib/adafruit_display_text/scrolling_label.py new file mode 100644 index 0000000..b4e4e0a --- /dev/null +++ b/src/lib/adafruit_display_text/scrolling_label.py @@ -0,0 +1,160 @@ +# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_display_text.scrolling_label` +==================================================== + +Displays text into a fixed-width label that scrolls leftward +if the full_text is large enough to need it. + +* Author(s): Tim Cocks + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +""" + +__version__ = "3.2.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" + +import adafruit_ticks +from adafruit_display_text import bitmap_label + +try: + from typing import Optional + from fontio import FontProtocol +except ImportError: + pass + + +class ScrollingLabel(bitmap_label.Label): + """ScrollingLabel - A fixed-width label that will scroll to the left + in order to show the full text if it's larger than the fixed-width. + + :param font: The font to use for the label. + :type: ~fontio.FontProtocol + :param int max_characters: The number of characters that sets the fixed-width. Default is 10. + :param str text: The full text to show in the label. If this is longer than + ``max_characters`` then the label will scroll to show everything. + :param float animate_time: The number of seconds in between scrolling animation + frames. Default is 0.3 seconds. + :param int current_index: The index of the first visible character in the label. + Default is 0, the first character. Will increase while scrolling.""" + + # pylint: disable=too-many-arguments + def __init__( + self, + font: FontProtocol, + max_characters: int = 10, + text: Optional[str] = "", + animate_time: Optional[float] = 0.3, + current_index: Optional[int] = 0, + **kwargs + ) -> None: + super().__init__(font, **kwargs) + self.animate_time = animate_time + self._current_index = current_index + self._last_animate_time = -1 + self.max_characters = max_characters + + if text and text[-1] != " ": + text = "{} ".format(text) + self._full_text = text + + self.update() + + def update(self, force: bool = False) -> None: + """Attempt to update the display. If ``animate_time`` has elapsed since + previews animation frame then move the characters over by 1 index. + Must be called in the main loop of user code. + + :param bool force: whether to ignore ``animation_time`` and force the update. + Default is False. + :return: None + """ + _now = adafruit_ticks.ticks_ms() + if force or adafruit_ticks.ticks_less( + self._last_animate_time + int(self.animate_time * 1000), _now + ): + if len(self.full_text) <= self.max_characters: + if self._text != self.full_text: + super()._set_text(self.full_text, self.scale) + self._last_animate_time = _now + return + + if self.current_index + self.max_characters <= len(self.full_text): + _showing_string = self.full_text[ + self.current_index : self.current_index + self.max_characters + ] + else: + _showing_string_start = self.full_text[self.current_index :] + _showing_string_end = "{}".format( + self.full_text[ + : (self.current_index + self.max_characters) + % len(self.full_text) + ] + ) + + _showing_string = "{}{}".format( + _showing_string_start, _showing_string_end + ) + super()._set_text(_showing_string, self.scale) + self.current_index += 1 + self._last_animate_time = _now + + return + + @property + def current_index(self) -> int: + """Index of the first visible character. + + :return int: The current index + """ + return self._current_index + + @current_index.setter + def current_index(self, new_index: int) -> None: + if self.full_text: + self._current_index = new_index % len(self.full_text) + else: + self._current_index = 0 + + @property + def full_text(self) -> str: + """The full text to be shown. If it's longer than ``max_characters`` then + scrolling will occur as needed. + + :return str: The full text of this label. + """ + return self._full_text + + @full_text.setter + def full_text(self, new_text: str) -> None: + if new_text and new_text[-1] != " ": + new_text = "{} ".format(new_text) + if new_text != self._full_text: + self._full_text = new_text + self.current_index = 0 + self.update(True) + + @property + def text(self): + """The full text to be shown. If it's longer than ``max_characters`` then + scrolling will occur as needed. + + :return str: The full text of this label. + """ + return self.full_text + + @text.setter + def text(self, new_text): + self.full_text = new_text diff --git a/src/lib/adafruit_display_text/text_box.py b/src/lib/adafruit_display_text/text_box.py new file mode 100644 index 0000000..58f668b --- /dev/null +++ b/src/lib/adafruit_display_text/text_box.py @@ -0,0 +1,435 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_display_text.text_box` +================================================================================ + +Text graphics handling for CircuitPython, including text boxes + + +* Author(s): Tim Cocks + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +""" + +__version__ = "3.2.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" + +import displayio +from micropython import const + +from adafruit_display_text import wrap_text_to_pixels +from adafruit_display_text import bitmap_label + +try: + from typing import Optional, Tuple + from fontio import FontProtocol +except ImportError: + pass + + +# pylint: disable=too-many-instance-attributes, duplicate-code +class TextBox(bitmap_label.Label): + """ + TextBox has a constrained width and optionally height. + You set the desired size when it's initialized it + will automatically wrap text to fit it within the allotted + size. + + Left, Right, and Center alignment of the text within the + box are supported. + + :param font: The font to use for the TextBox. + :param width: The width of the TextBox in pixels. + :param height: The height of the TextBox in pixels. + :param align: How to align the text within the box, + valid values are ``ALIGN_LEFT``, ``ALIGN_CENTER``, ``ALIGN_RIGHT``. + """ + + ALIGN_LEFT = const(0) + ALIGN_CENTER = const(1) + ALIGN_RIGHT = const(2) + + DYNAMIC_HEIGHT = const(-1) + + def __init__( + self, font: FontProtocol, width: int, height: int, align=ALIGN_LEFT, **kwargs + ) -> None: + self._bitmap = None + self._tilegrid = None + self._prev_label_direction = None + self._width = width + + if height != TextBox.DYNAMIC_HEIGHT: + self._height = height + self.dynamic_height = False + else: + self.dynamic_height = True + + if align not in (TextBox.ALIGN_LEFT, TextBox.ALIGN_CENTER, TextBox.ALIGN_RIGHT): + raise ValueError( + "Align must be one of: ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT" + ) + self._align = align + + self._padding_left = kwargs.get("padding_left", 0) + self._padding_right = kwargs.get("padding_right", 0) + + self.lines = wrap_text_to_pixels( + kwargs.get("text", ""), + self._width - self._padding_left - self._padding_right, + font, + ) + + super(bitmap_label.Label, self).__init__(font, **kwargs) + + print(f"before reset: {self._text}") + + self._text = "\n".join(self.lines) + self._text = self._replace_tabs(self._text) + self._original_text = self._text + + # call the text updater with all the arguments. + self._reset_text( + font=font, + text=self._text, + line_spacing=self._line_spacing, + scale=self.scale, + ) + print(f"after reset: {self._text}") + + def _place_text( + self, + bitmap: displayio.Bitmap, + text: str, + font: FontProtocol, + xposition: int, + yposition: int, + skip_index: int = 0, # set to None to write all pixels, other wise skip this palette index + # when copying glyph bitmaps (this is important for slanted text + # where rectangular glyph boxes overlap) + ) -> Tuple[int, int, int, int]: + # pylint: disable=too-many-arguments, too-many-locals, too-many-statements, too-many-branches + + # placeText - Writes text into a bitmap at the specified location. + # + # Note: scale is pushed up to Group level + original_xposition = xposition + cur_line_index = 0 + cur_line_width = self._text_bounding_box(self.lines[0], self.font)[0] + + if self.align == self.ALIGN_LEFT: + x_start = original_xposition # starting x position (left margin) + if self.align == self.ALIGN_CENTER: + unused_space = self._width - cur_line_width + x_start = original_xposition + unused_space // 2 + if self.align == self.ALIGN_RIGHT: + unused_space = self._width - cur_line_width + x_start = original_xposition + unused_space - self._padding_right + + xposition = x_start # pylint: disable=used-before-assignment + + y_start = yposition + # print(f"start loc {x_start}, {y_start}") + + left = None + right = x_start + top = bottom = y_start + line_spacing = self._line_spacing + + # print(f"cur_line width: {cur_line_width}") + for char in text: + if char == "\n": # newline + cur_line_index += 1 + cur_line_width = self._text_bounding_box( + self.lines[cur_line_index], self.font + )[0] + # print(f"cur_line width: {cur_line_width}") + if self.align == self.ALIGN_LEFT: + x_start = original_xposition # starting x position (left margin) + if self.align == self.ALIGN_CENTER: + unused_space = self._width - cur_line_width + x_start = original_xposition + unused_space // 2 + if self.align == self.ALIGN_RIGHT: + unused_space = self._width - cur_line_width + x_start = original_xposition + unused_space - self._padding_right + xposition = x_start + + yposition = yposition + self._line_spacing_ypixels( + font, line_spacing + ) # Add a newline + + else: + my_glyph = font.get_glyph(ord(char)) + + if my_glyph is None: # Error checking: no glyph found + print("Glyph not found: {}".format(repr(char))) + else: + if xposition == x_start: + if left is None: + left = 0 + else: + left = min(left, my_glyph.dx) + + right = max( + right, + xposition + my_glyph.shift_x, + xposition + my_glyph.width + my_glyph.dx, + ) + if yposition == y_start: # first line, find the Ascender height + top = min(top, -my_glyph.height - my_glyph.dy) + bottom = max(bottom, yposition - my_glyph.dy) + + glyph_offset_x = ( + my_glyph.tile_index * my_glyph.width + ) # for type BuiltinFont, this creates the x-offset in the glyph bitmap. + # for BDF loaded fonts, this should equal 0 + + y_blit_target = yposition - my_glyph.height - my_glyph.dy + + # Clip glyph y-direction if outside the font ascent/descent metrics. + # Note: bitmap.blit will automatically clip the bottom of the glyph. + y_clip = 0 + if y_blit_target < 0: + y_clip = -y_blit_target # clip this amount from top of bitmap + y_blit_target = 0 # draw the clipped bitmap at y=0 + if self._verbose: + print( + 'Warning: Glyph clipped, exceeds Ascent property: "{}"'.format( + char + ) + ) + + if (y_blit_target + my_glyph.height) > bitmap.height: + if self._verbose: + print( + 'Warning: Glyph clipped, exceeds descent property: "{}"'.format( + char + ) + ) + try: + self._blit( + bitmap, + max(xposition + my_glyph.dx, 0), + y_blit_target, + my_glyph.bitmap, + x_1=glyph_offset_x, + y_1=y_clip, + x_2=glyph_offset_x + my_glyph.width, + y_2=my_glyph.height, + skip_index=skip_index, # do not copy over any 0 background pixels + ) + except ValueError: + # ignore index out of bounds error + break + + xposition = xposition + my_glyph.shift_x + + # bounding_box + return left, top, right - left, bottom - top + + def _reset_text( + self, + font: Optional[FontProtocol] = None, + text: Optional[str] = None, + line_spacing: Optional[float] = None, + scale: Optional[int] = None, + ) -> None: + # pylint: disable=too-many-branches, too-many-statements, too-many-locals + + # Store all the instance variables + if font is not None: + self._font = font + if line_spacing is not None: + self._line_spacing = line_spacing + + # if text is not provided as a parameter (text is None), use the previous value. + if text is None: + text = self._text + + self._text = self._replace_tabs(text) + print(f"inside reset_text text: {text}") + + # Check for empty string + if (text == "") or ( + text is None + ): # If empty string, just create a zero-sized bounding box and that's it. + self._bounding_box = ( + 0, + 0, + 0, # zero width with text == "" + 0, # zero height with text == "" + ) + # Clear out any items in the self._local_group Group, in case this is an + # update to the bitmap_label + for _ in self._local_group: + self._local_group.pop(0) + + # Free the bitmap and tilegrid since they are removed + self._bitmap = None + self._tilegrid = None + + else: # The text string is not empty, so create the Bitmap and TileGrid and + # append to the self Group + + # Calculate the text bounding box + + # Calculate both "tight" and "loose" bounding box dimensions to match label for + # anchor_position calculations + ( + box_x, + tight_box_y, + x_offset, + tight_y_offset, + loose_box_y, + loose_y_offset, + ) = self._text_bounding_box( + text, + self._font, + ) # calculate the box size for a tight and loose backgrounds + + if self._background_tight: + box_y = tight_box_y + y_offset = tight_y_offset + self._padding_left = 0 + self._padding_right = 0 + self._padding_top = 0 + self._padding_bottom = 0 + + else: # calculate the box size for a loose background + box_y = loose_box_y + y_offset = loose_y_offset + + # Calculate the background size including padding + tight_box_x = box_x + box_x = box_x + self._padding_left + self._padding_right + box_y = box_y + self._padding_top + self._padding_bottom + + if self.dynamic_height: + print(f"dynamic height, box_y: {box_y}") + self._height = box_y + + # Create the Bitmap unless it can be reused + new_bitmap = None + if ( + self._bitmap is None + or self._bitmap.width != self._width + or self._bitmap.height != self._height + ): + new_bitmap = displayio.Bitmap( + self._width, self._height, len(self._palette) + ) + self._bitmap = new_bitmap + else: + self._bitmap.fill(0) + + # Place the text into the Bitmap + self._place_text( + self._bitmap, + text, + self._font, + self._padding_left - x_offset, + self._padding_top + y_offset, + ) + + if self._base_alignment: + label_position_yoffset = 0 + else: + label_position_yoffset = self._ascent // 2 + + # Create the TileGrid if not created bitmap unchanged + if self._tilegrid is None or new_bitmap: + self._tilegrid = displayio.TileGrid( + self._bitmap, + pixel_shader=self._palette, + width=1, + height=1, + tile_width=self._width, + tile_height=self._height, + default_tile=0, + x=-self._padding_left + x_offset, + y=label_position_yoffset - y_offset - self._padding_top, + ) + # Clear out any items in the local_group Group, in case this is an update to + # the bitmap_label + for _ in self._local_group: + self._local_group.pop(0) + self._local_group.append( + self._tilegrid + ) # add the bitmap's tilegrid to the group + + self._bounding_box = ( + self._tilegrid.x + self._padding_left, + self._tilegrid.y + self._padding_top, + tight_box_x, + tight_box_y, + ) + print(f"end of reset_text bounding box: {self._bounding_box}") + + if ( + scale is not None + ): # Scale will be defined in local_group (Note: self should have scale=1) + self.scale = scale # call the setter + + # set the anchored_position with setter after bitmap is created, sets the + # x,y positions of the label + self.anchored_position = self._anchored_position + + @property + def height(self) -> int: + """The height of the label determined from the bounding box.""" + return self._height + + @property + def width(self) -> int: + """The width of the label determined from the bounding box.""" + return self._width + + @width.setter + def width(self, width: int) -> None: + self._width = width + self.text = self._text + + @height.setter + def height(self, height: int) -> None: + if height != TextBox.DYNAMIC_HEIGHT: + self._height = height + self.dynamic_height = False + else: + self.dynamic_height = True + self.text = self._text + + @bitmap_label.Label.text.setter + def text(self, text: str) -> None: + self.lines = wrap_text_to_pixels( + text, self._width - self._padding_left - self._padding_right, self.font + ) + self._text = self._replace_tabs(text) + self._original_text = self._text + self._text = "\n".join(self.lines) + + self._set_text(self._text, self.scale) + + @property + def align(self): + """Alignment of the text within the TextBox""" + return self._align + + @align.setter + def align(self, align: int) -> None: + if align not in (TextBox.ALIGN_LEFT, TextBox.ALIGN_CENTER, TextBox.ALIGN_RIGHT): + raise ValueError( + "Align must be one of: ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT" + ) + self._align = align diff --git a/src/lib/adafruit_ds3231.mpy b/src/lib/adafruit_ds3231.mpy new file mode 100644 index 0000000000000000000000000000000000000000..215d53d28bcefae1fc179d71727e42e86fda0e11 GIT binary patch literal 1225 zcmZXSTW{k;6vxMDnu{@w-Gm{e>DKEyVK=l1b-iK5f|PB71QnO0ELtLgCeGMxjM6x= zC%cNogA*;g6-X4s6HmPCGs?d4ZTJ|xa>h-nA{;sMJLml8T*lK(pq=*w-LkDyf8sJ? z2ZzStL3KPunaxj(X3KfvQU`r-9q&&9Vn@!ZYtSX44bFX+A?;e|J7;dd93RQcSbz!< zQJYvp%Rfiz%Cs%!F!$U+5}S_Q(HTlWWh6o=*F!j9{)93T2~t-wqiR--10=P+1IMf^20g|Haum>dl>${YC3F2Z*o8A#5UF`Vg8 z;qsgI=b4LUSgJHCjl1yU>puVQwI_|ru|4R0*BkVnr7uLKg5Qxdcu$GvwRJD?>m5#u>+k9}c$m<;S=7U96 grM Tuple[Bitmap, Optional[Union[Palette, ColorConverter]]]: + """Load pixel values (indices or colors) into a bitmap and colors into a palette. + + bitmap is the desired type. It must take width, height and color_depth in the constructor. It + must also have a _load_row method to load a row's worth of pixel data. + + palette is the desired palette type. The constructor should take the number of colors and + support assignment to indices via []. + """ + if not bitmap or not palette: + try: + # use displayio if available + import displayio + + if not bitmap: + bitmap = displayio.Bitmap + if not palette: + palette = displayio.Palette + except ModuleNotFoundError: + # meh, we tried + pass + + if isinstance(file_or_filename, str): + open_file = open(file_or_filename, "rb") + else: + open_file = file_or_filename + + with open_file as file: + header = file.read(3) + file.seek(0) + if header.startswith(b"BM"): + from . import bmp + + return bmp.load(file, bitmap=bitmap, palette=palette) + if header.startswith(b"P"): + from . import pnm + + return pnm.load(file, header, bitmap=bitmap, palette=palette) + if header.startswith(b"GIF"): + if not bitmap: + raise RuntimeError("bitmap argument required") + + from . import gif + + return gif.load(file, bitmap=bitmap, palette=palette) + if header.startswith(b"\x89PN"): + if not bitmap: + raise RuntimeError("bitmap argument required") + from . import png + + return png.load(file, bitmap=bitmap, palette=palette) + if header.startswith(b"\xff\xd8"): + from . import jpg + + return jpg.load(file, bitmap=bitmap) + raise RuntimeError("Unsupported image format") diff --git a/src/lib/adafruit_imageload/bmp/__init__.py b/src/lib/adafruit_imageload/bmp/__init__.py new file mode 100644 index 0000000..00487b2 --- /dev/null +++ b/src/lib/adafruit_imageload/bmp/__init__.py @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2022-2023 Matt Land +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.bmp` +==================================================== + +Load pixel values (indices or colors) into a bitmap and colors into a palette from a BMP file. + +* Author(s): Scott Shawcroft, Matt Land + +""" + +try: + from io import BufferedReader + from typing import List, Optional, Set, Tuple, Union + + from displayio import Bitmap, ColorConverter, Palette + + from ..displayio_types import BitmapConstructor, PaletteConstructor +except ImportError: + pass + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + + +def load( + file: BufferedReader, + *, + bitmap: Optional[BitmapConstructor] = None, + palette: Optional[PaletteConstructor] = None, +) -> Tuple[Optional[Bitmap], Optional[Union[Palette, ColorConverter]]]: + """Loads a bmp image from the open ``file``. + + Returns tuple of `displayio.Bitmap` object and + `displayio.Palette` object, or `displayio.ColorConverter` object. + + :param io.BufferedReader file: Open file handle or compatible (like `io.BytesIO`) + with the data of a BMP file. + :param object bitmap: Type to store bitmap data. Must have API similar to `displayio.Bitmap`. + Will be skipped if None + :param object palette: Type to store the palette. Must have API similar to + `displayio.Palette`. Will be skipped if None""" + file.seek(10) + data_start = int.from_bytes(file.read(4), "little") + file.seek(14) + bmp_header_length = int.from_bytes(file.read(4), "little") + # print(bmp_header_length) + file.seek(0x12) # Width of the bitmap in pixels + _width = int.from_bytes(file.read(4), "little") + try: + _height = int.from_bytes(file.read(4), "little") + except OverflowError as error: + raise NotImplementedError( + "Negative height BMP files are not supported on builds without longint" + ) from error + file.seek(0x1C) # Number of bits per pixel + color_depth = int.from_bytes(file.read(2), "little") + file.seek(0x1E) # Compression type + compression = int.from_bytes(file.read(2), "little") + file.seek(0x2E) # Number of colors in the color palette + colors = int.from_bytes(file.read(4), "little") + bitfield_masks = None + if compression == 3 and bmp_header_length >= 56: + bitfield_masks = {} + endianess = "little" if color_depth == 16 else "big" + file.seek(0x36) + bitfield_masks["red"] = int.from_bytes(file.read(4), endianess) + file.seek(0x3A) + bitfield_masks["green"] = int.from_bytes(file.read(4), endianess) + file.seek(0x3E) + bitfield_masks["blue"] = int.from_bytes(file.read(4), endianess) + + if compression > 3: + raise NotImplementedError("bitmask compression unsupported") + + if colors == 0 and color_depth >= 16: + from . import truecolor + + return truecolor.load( + file, + _width, + _height, + data_start, + color_depth, + bitfield_masks, + bitmap=bitmap, + ) + if colors == 0: + colors = 2**color_depth + from . import indexed + + return indexed.load( + file, + _width, + _height, + data_start, + colors, + color_depth, + compression, + bitmap=bitmap, + palette=palette, + ) diff --git a/src/lib/adafruit_imageload/bmp/indexed.py b/src/lib/adafruit_imageload/bmp/indexed.py new file mode 100644 index 0000000..044cd51 --- /dev/null +++ b/src/lib/adafruit_imageload/bmp/indexed.py @@ -0,0 +1,265 @@ +# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2022-2023 Matt Land +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.bmp.indexed` +==================================================== + +Load pixel values (indices or colors) into a bitmap and colors into a palette from an indexed BMP. + +* Author(s): Scott Shawcroft, Matt Land + +""" + +import sys + +try: + from io import BufferedReader + from typing import Optional, Tuple + + from displayio import Bitmap, Palette + + from ..displayio_types import BitmapConstructor, PaletteConstructor +except ImportError: + pass + +try: + from bitmaptools import readinto as _bitmap_readinto +except ImportError: + _bitmap_readinto = None + + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + + +def load( # noqa: PLR0913, PLR0912, Too many arguments in function definition, Too many branches + file: BufferedReader, + width: int, + height: int, + data_start: int, + colors: int, + color_depth: int, + compression: int, + *, + bitmap: Optional[BitmapConstructor] = None, + palette: Optional[PaletteConstructor] = None, +) -> Tuple[Optional[Bitmap], Optional[Palette]]: + """Loads indexed bitmap data into bitmap and palette objects. + + :param file file: The open bmp file + :param int width: Image width in pixels + :param int height: Image height in pixels + :param int data_start: Byte location where the data starts (after headers) + :param int colors: Number of distinct colors in the image + :param int color_depth: Number of bits used to store a value + :param int compression: 0 - none, 1 - 8bit RLE, 2 - 4bit RLE + :param BitmapConstructor bitmap: a function that returns a displayio.Bitmap + :param PaletteConstructor palette: a function that returns a displayio.Palette + """ + palette_obj = None + if palette: + palette_obj = palette(colors) + + file.seek(data_start - colors * 4) + for value in range(colors): + c_bytes = file.read(4) + # Need to swap red & blue bytes (bytes 0 and 2) + palette_obj[value] = bytes( + b"".join([c_bytes[2:3], c_bytes[1:2], c_bytes[0:1], c_bytes[3:1]]) + ) + + bitmap_obj = None + if bitmap: + minimum_color_depth = 1 + while colors > 2**minimum_color_depth: + minimum_color_depth *= 2 + + if sys.maxsize > 1073741823: + from .negative_height_check import negative_height_check + + # convert unsigned int to signed int when height is negative + height = negative_height_check(height) + bitmap_obj = bitmap(width, abs(height), colors) + file.seek(data_start) + line_size = width // (8 // color_depth) + if width % (8 // color_depth) != 0: + line_size += 1 + if line_size % 4 != 0: + line_size += 4 - line_size % 4 + + mask = (1 << minimum_color_depth) - 1 + if height > 0: + range1 = height - 1 + range2 = -1 + range3 = -1 + else: + range1 = 0 + range2 = abs(height) + range3 = 1 + + if compression == 0: + if _bitmap_readinto: + _bitmap_readinto( + bitmap_obj, + file, + bits_per_pixel=color_depth, + element_size=4, + reverse_pixels_in_element=True, + reverse_rows=True, + ) + + else: # use the standard file.readinto + chunk = bytearray(line_size) + for y in range(range1, range2, range3): + file.readinto(chunk) + pixels_per_byte = 8 // color_depth + offset = y * width + + for x in range(width): + i = x // pixels_per_byte + pixel = (chunk[i] >> (8 - color_depth * (x % pixels_per_byte + 1))) & mask + bitmap_obj[offset + x] = pixel + elif compression in (1, 2): + decode_rle( + bitmap=bitmap_obj, + file=file, + compression=compression, + y_range=(range1, range2, range3), + width=width, + ) + + return bitmap_obj, palette_obj + + +def decode_rle( # noqa: PLR0912 Too many branches + bitmap: Bitmap, + file: BufferedReader, + compression: int, + y_range: Tuple[int, int, int], + width: int, +) -> None: + """Helper to decode RLE images""" + + # RLE algorithm, either 8-bit (1) or 4-bit (2) + # + # Ref: http://www.fileformat.info/format/bmp/egff.htm + + is_4bit = compression == 2 + + # This will store the 2-byte run commands, which are either an + # amount to repeat and a value to repeat, or a 0x00 and command + # marker. + run_buf = bytearray(2) + + # We need to be prepared to load up to 256 pixels of literal image + # data. (0xFF is max literal length, but odd literal runs are padded + # up to an even byte count, so we need space for 256 in the case of + # 8-bit.) 4-bit images can get away with half that. + literal_buf = bytearray(128 if is_4bit else 256) + + # We iterate with numbers rather than a range because the "delta" + # command can cause us to jump forward arbitrarily in the output + # image. + # + # In theory RLE images are only stored in bottom-up scan line order, + # but we support either. + (range1, range2, range3) = y_range + y = range1 + x = 0 + + while y * range3 < range2 * range3: + offset = y * width + x + + # We keep track of how much space is left in our row so that we + # can avoid writing extra data outside of the Bitmap. While the + # reference above seems to say that the "end run" command is + # optional and that image data should wrap from one scan line to + # the next, in practice (looking at the output of ImageMagick + # and GIMP, and what Preview renders) the bitmap part of the + # image can contain data that goes beyond the image’s stated + # width that should just be ignored. For example, the 8bit RLE + # file is 15px wide but has data for 16px. + width_remaining = width - x + + file.readinto(run_buf) + + if run_buf[0] == 0: + # A repeat length of "0" is a special command. The next byte + # tells us what needs to happen. + if run_buf[1] == 0: + # end of the current scan line + y = y + range3 + x = 0 + elif run_buf[1] == 1: + # end of image + break + elif run_buf[1] == 2: + # delta command jumps us ahead in the bitmap output by + # the x, y amounts stored in the next 2 bytes. + file.readinto(run_buf) + + x = x + run_buf[0] + y = y + run_buf[1] * range3 + else: + # command values of 3 or more indicate that many pixels + # of literal (uncompressed) image data. For 8-bit mode, + # this is raw bytes, but 4-bit mode counts in nibbles. + literal_length_px = run_buf[1] + + # Inverting the value here to get round-up integer division + if is_4bit: + read_length_bytes = -(-literal_length_px // 2) + else: + read_length_bytes = literal_length_px + + # If the run has an odd length then there’s a 1-byte padding + # we need to consume but not write into the output + if read_length_bytes % 2 == 1: + read_length_bytes += 1 + + # We use memoryview to artificially limit the length of + # literal_buf so that readinto only reads the amount + # that we want. + literal_buf_mem = memoryview(literal_buf) + file.readinto(literal_buf_mem[0:read_length_bytes]) + + if is_4bit: + for i in range(0, min(literal_length_px, width_remaining)): + # Expanding the two nibbles of the 4-bit data + # into two bytes for our output bitmap. + if i % 2 == 0: + bitmap[offset + i] = literal_buf[i // 2] >> 4 + else: + bitmap[offset + i] = literal_buf[i // 2] & 0x0F + else: + # 8-bit values are just a raw copy (limited by + # what’s left in the row so we don’t overflow out of + # the buffer) + for i in range(0, min(literal_length_px, width_remaining)): + bitmap[offset + i] = literal_buf[i] + + x = x + literal_length_px + else: + # first byte was not 0, which means it tells us how much to + # repeat the next byte into the output + run_length_px = run_buf[0] + + if is_4bit: + # In 4 bit mode, we repeat the *two* values that are + # packed into the next byte. The repeat amount is based + # on pixels, not bytes, though, so if we were to repeat + # 0xab 3 times, the output pixel values would be: 0x0a + # 0x0b 0x0a (notice how it ends at 0x0a) rather than + # 0x0a 0x0b 0x0a 0x0b 0x0a 0x0b + run_values = [run_buf[1] >> 4, run_buf[1] & 0x0F] + for i in range(0, min(run_length_px, width_remaining)): + bitmap[offset + i] = run_values[i % 2] + else: + run_value = run_buf[1] + for i in range(0, min(run_length_px, width_remaining)): + bitmap[offset + i] = run_value + + x = x + run_length_px diff --git a/src/lib/adafruit_imageload/bmp/negative_height_check.py b/src/lib/adafruit_imageload/bmp/negative_height_check.py new file mode 100644 index 0000000..f16b79e --- /dev/null +++ b/src/lib/adafruit_imageload/bmp/negative_height_check.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2022 Matt Land +# +# SPDX-License-Identifier: MIT + +""" +Check for negative height on the BMP. +Seperated into it's own file to support builds +without longint. + +* Author(s): Tim Cocks, Matt Land +""" + + +def negative_height_check(height: int) -> int: + """Check the height return modified if negative.""" + if height > 0x7FFFFFFF: + return height - 4294967296 + return height diff --git a/src/lib/adafruit_imageload/bmp/truecolor.py b/src/lib/adafruit_imageload/bmp/truecolor.py new file mode 100644 index 0000000..8f17b79 --- /dev/null +++ b/src/lib/adafruit_imageload/bmp/truecolor.py @@ -0,0 +1,131 @@ +# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2022-2023 Melissa LeBlanc-Williams +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.bmp.truecolor` +==================================================== + +Load pixel colors into a bitmap from an truecolor BMP and return the correct colorconverter. + +* Author(s): Melissa LeBlanc-Williams + +""" + +import sys + +try: + from io import BufferedReader + from typing import Optional, Tuple, Union + + from ..displayio_types import BitmapConstructor +except ImportError: + pass + +from displayio import Bitmap, ColorConverter, Colorspace + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + +bitfield_colorspaces = ( + { # 16-bit RGB555 + "mask_values": (0x00007C00, 0x000003E0, 0x0000001F), + "color_space": Colorspace.RGB555, + }, + { # 16-bit RGB565 + "mask_values": (0x0000F800, 0x000007E0, 0x0000001F), + "color_space": Colorspace.RGB565, + }, + { # 24 or 32-bit RGB888 (Alpha ignored for 32-bit) + "mask_values": (0x0000FF00, 0x00FF0000, 0xFF000000), + "color_space": Colorspace.RGB888, + }, +) + + +def bitfield_format(bitfield_mask): + """Returns the colorspace for the given bitfield mask""" + mask = (bitfield_mask["red"], bitfield_mask["green"], bitfield_mask["blue"]) + for colorspace in bitfield_colorspaces: + if colorspace["mask_values"] == mask: + return colorspace["color_space"] + return None + + +def load( # noqa: PLR0912, PLR0913, Too many branches, Too many arguments in function definition + file: BufferedReader, + width: int, + height: int, + data_start: int, + color_depth: int, + bitfield_masks: Union[dict, None], + *, + bitmap: Optional[BitmapConstructor] = None, +) -> Tuple[Optional[Bitmap], Optional[ColorConverter]]: + """Loads truecolor bitmap data into bitmap and palette objects. Due to the 16-bit limit + that the bitmap object can hold, colors will be converted to 16-bit RGB565 values. + + :param file file: The open bmp file + :param int width: Image width in pixels + :param int height: Image height in pixels + :param int data_start: Byte location where the data starts (after headers) + :param int color_depth: Number of bits used to store a value + :param dict bitfield_masks: The bitfield masks for each color if using bitfield compression + :param BitmapConstructor bitmap: a function that returns a displayio.Bitmap + """ + converter_obj = None + bitmap_obj = None + if bitmap: + # Set up a ColorConverter object and set appropriate colorspace + # to convert from based on the color depth + input_colorspace = Colorspace.RGB888 + if bitfield_masks is not None: + colorspace = bitfield_format(bitfield_masks) + if colorspace is not None: + input_colorspace = colorspace + else: + raise NotImplementedError("Bitfield mask not supported") + elif color_depth == 16: + input_colorspace = Colorspace.RGB555 + converter_obj = ColorConverter(input_colorspace=input_colorspace) + if sys.maxsize > 1073741823: + from .negative_height_check import negative_height_check + + # convert unsigned int to signed int when height is negative + height = negative_height_check(height) + bitmap_obj = bitmap(width, abs(height), 65535) + file.seek(data_start) + line_size = width * (color_depth // 8) + # Set the seek direction based on whether the height value is negative or positive + if height > 0: + range1 = height - 1 + range2 = -1 + range3 = -1 + else: + range1 = 0 + range2 = abs(height) + range3 = 1 + chunk = bytearray(line_size) + for y in range(range1, range2, range3): + file.readinto(chunk) + bytes_per_pixel = color_depth // 8 + offset = y * width + + for x in range(width): + i = x * bytes_per_pixel + if bitfield_masks is not None: + color = 0 + for byte in range(bytes_per_pixel): + color |= chunk[i + byte] << (8 * byte) + mask = bitfield_masks["red"] | bitfield_masks["green"] | bitfield_masks["blue"] + if color_depth in (24, 32): + mask = mask >> 8 + pixel = color & mask + elif color_depth == 16: + pixel = chunk[i] | chunk[i + 1] << 8 + else: + pixel = chunk[i + 2] << 16 | chunk[i + 1] << 8 | chunk[i] + bitmap_obj[offset + x] = converter_obj.convert(pixel) + + return bitmap_obj, ColorConverter(input_colorspace=Colorspace.RGB565) diff --git a/src/lib/adafruit_imageload/displayio_types.py b/src/lib/adafruit_imageload/displayio_types.py new file mode 100644 index 0000000..bed5efe --- /dev/null +++ b/src/lib/adafruit_imageload/displayio_types.py @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2022 Matt Land +# +# SPDX-License-Identifier: MIT +""" +`adafruit_imageload.displayio_types` +==================================================== + +This is a utility file for type aliases. +https://mypy.readthedocs.io/en/stable/kinds_of_types.html#type-aliases +Type aliases contain compound declarations (used many places in the project) with a single +definition readable by humans. + +* Author(s): Matt Land + +""" + +try: + from typing import Callable + + from displayio import Bitmap, Palette + + PaletteConstructor = Callable[[int], Palette] + BitmapConstructor = Callable[[int, int, int], Bitmap] +except ImportError: + pass + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" diff --git a/src/lib/adafruit_imageload/gif.py b/src/lib/adafruit_imageload/gif.py new file mode 100644 index 0000000..c3099fe --- /dev/null +++ b/src/lib/adafruit_imageload/gif.py @@ -0,0 +1,169 @@ +# SPDX-FileCopyrightText: 2019 Radomir Dopieralski for Adafruit Industries +# SPDX-FileCopyrightText: 2022-2023 Matt Land +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.gif` +==================================================== + +Load pixel values (indices or colors) into a bitmap and colors into a palette +from a GIF file. + +* Author(s): Radomir Dopieralski, Matt Land + +""" + +import struct + +try: + from io import BufferedReader + from typing import Iterator, List, Optional, Tuple + + from displayio import Bitmap, Palette + + from .displayio_types import BitmapConstructor, PaletteConstructor +except ImportError: + pass + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + + +def load( + file: BufferedReader, *, bitmap: BitmapConstructor, palette: Optional[PaletteConstructor] = None +) -> Tuple[Bitmap, Optional[Palette]]: + """Loads a GIF image from the open ``file``. + + Returns tuple of bitmap object and palette object. + + :param io.BufferedReader file: Open file handle or compatible (like `io.BytesIO`) + with the data of a GIF file. + :param object bitmap: Type to store bitmap data. Must have API similar to `displayio.Bitmap`. + :param object palette: Type to store the palette. Must have API similar to + `displayio.Palette`. Will be skipped if None. + """ + header = file.read(6) + if header not in {b"GIF87a", b"GIF89a"}: + raise ValueError("Not a GIF file") + width, height, flags, _, _ = struct.unpack("> 4) + 1 + bitmap_obj = bitmap(width, height, (1 << color_bits) - 1) + while True: + block_type = file.read(1)[0] + if block_type == 0x2C: # frame + _read_frame(file, bitmap_obj) + elif block_type == 0x21: # extension + _ = file.read(1)[0] + # 0x01 = label, 0xfe = comment + _ = bytes(_read_blockstream(file)) + elif block_type == 0x3B: # terminator + break + else: + raise ValueError("Bad block type") + return bitmap_obj, palette_obj + + +def _read_frame(file: BufferedReader, bitmap: Bitmap) -> None: + """Read a single frame and apply it to the bitmap.""" + ddx, ddy, width, _, flags = struct.unpack("= width: + x = 0 + y += 1 + + +def _read_blockstream(file: BufferedReader) -> Iterator[int]: + """Read a block from a file.""" + while True: + size = file.read(1)[0] + if size == 0: + break + for _ in range(size): + yield file.read(1)[0] + + +class EndOfData(Exception): + """Signified end of compressed data.""" + + +class LZWDict: + """A dictionary of LZW codes.""" + + def __init__(self, code_size: int) -> None: + self.code_size = code_size + self.clear_code = 1 << code_size + self.end_code = self.clear_code + 1 + self.codes = [] # type: List[bytes] + self.last = b"" + self.clear() + + def clear(self) -> None: + """Reset the dictionary to default codes.""" + self.last = b"" + self.code_len = self.code_size + 1 + self.codes[:] = [] + + def decode(self, code: int) -> bytes: + """Decode a code.""" + if code == self.clear_code: + self.clear() + return b"" + if code == self.end_code: + raise EndOfData() + if code < self.clear_code: + value = bytes([code]) + elif code <= len(self.codes) + self.end_code: + value = self.codes[code - self.end_code - 1] + else: + value = self.last + self.last[0:1] + if self.last: + self.codes.append(self.last + value[0:1]) + if len(self.codes) + self.end_code + 1 >= 1 << self.code_len and self.code_len < 12: + self.code_len += 1 + self.last = value + return value + + +def lzw_decode(data: Iterator[int], code_size: int) -> Iterator[bytes]: + """Decode LZW-compressed data.""" + dictionary = LZWDict(code_size) + bit = 0 + try: + byte = next(data) + try: + while True: + code = 0 + for i in range(dictionary.code_len): + code |= ((byte >> bit) & 0x01) << i + bit += 1 + if bit >= 8: + bit = 0 + byte = next(data) + yield dictionary.decode(code) + except EndOfData: + while True: + next(data) + except StopIteration: + pass diff --git a/src/lib/adafruit_imageload/jpg.py b/src/lib/adafruit_imageload/jpg.py new file mode 100644 index 0000000..0f5d07d --- /dev/null +++ b/src/lib/adafruit_imageload/jpg.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: 2024 Channing Ramos +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.jpg` +==================================================== + +Load a JPG into a bitmap by calling the jpegio class. + +* Author(s): Channing Ramos + +""" + +# A separate try for jpegio. Not every board supports it and this import may fail. +# If that happens an ImportError with a proper message needs to be raised +try: + from jpegio import JpegDecoder +except ImportError: + print("jpegio not supported on this board.") + +try: + from io import BufferedReader + from typing import Optional, Tuple + + from .displayio_types import BitmapConstructor +except ImportError: + pass + +from displayio import Bitmap, ColorConverter, Colorspace + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + + +def load( + file: BufferedReader, + *, + bitmap: BitmapConstructor, +) -> Tuple[Bitmap, Optional[ColorConverter]]: + """ + Loads a JPG image from the open ''file''. + The JPG must be a Baseline JPG, Progressive and Lossless JPG formats are not supported. + + Returns tuple of bitmap object and ColorConverter object. + + :param io.BufferedReader file: Open file handle or compatible (like 'io.BytesIO') + :param object bitmap: Type to store bitmap data. + Must have API similar to 'displayio.Bitmap'. Will be skipped if None. + Will be skipped if None. + """ + decoder = JpegDecoder() + width, height = decoder.open(file) + bitmap_obj = bitmap(width, height, 65535) + decoder.decode(bitmap_obj) + + return bitmap_obj, ColorConverter(input_colorspace=Colorspace.RGB565_SWAPPED) diff --git a/src/lib/adafruit_imageload/png.py b/src/lib/adafruit_imageload/png.py new file mode 100644 index 0000000..55f6226 --- /dev/null +++ b/src/lib/adafruit_imageload/png.py @@ -0,0 +1,184 @@ +# SPDX-FileCopyrightText: 2022 Radomir Dopieralski +# SPDX-FileCopyrightText: 2023 Matt Land +# SPDX-FileCopyrightText: 2024 Channing Ramos +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.png` +==================================================== + +Load pixel values (indices or colors) into a bitmap and colors into a palette +from a PNG file. + +* Author(s): Radomir Dopieralski, Matt Land, Channing Ramos + +""" + +try: + from io import BufferedReader + from typing import Optional, Tuple + + from displayio import Bitmap, Palette + + from .displayio_types import BitmapConstructor, PaletteConstructor +except ImportError: + pass + +import struct +import zlib + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + + +def load( # noqa: PLR0912, PLR0915, Too many branches, Too many statements + file: BufferedReader, *, bitmap: BitmapConstructor, palette: Optional[PaletteConstructor] = None +) -> Tuple[Bitmap, Optional[Palette]]: + """ + Loads a PNG image from the open ``file``. + Only supports indexed color images. + + Returns tuple of bitmap object and palette object. + + :param io.BufferedReader file: Open file handle or compatible (like `io.BytesIO`) + with the data of a PNG file. + :param object bitmap: Type to store bitmap data. Must have API similar to + `displayio.Bitmap`. + :param object palette: Type to store the palette. Must have API similar to + `displayio.Palette`. Will be skipped if None. + """ + header = file.read(8) + if header != b"\x89PNG\r\n\x1a\n": + raise ValueError("Not a PNG file") + del header + data = bytearray() + pal = None + mode = None + depth = 0 + width = 0 + height = 0 + while True: + size, chunk = struct.unpack(">I4s", file.read(8)) + if chunk == b"IHDR": + ( + width, + height, + depth, + mode, + compression, + filters, + interlaced, + ) = struct.unpack(">IIBBBBB", file.read(13)) + if interlaced: + raise NotImplementedError("Interlaced images unsupported") + # compression and filters must be 0 with current spec + assert compression == 0 + assert filters == 0 + elif chunk == b"PLTE": + if palette is None: + file.seek(size, 1) + else: + if mode != 3: + raise NotImplementedError("Palette in non-indexed image") + pal_size = size // 3 + pal = palette(pal_size) + for i in range(pal_size): + pal[i] = file.read(3) + elif chunk == b"tRNS": + if size > len(pal): + raise ValueError("More transparency entries than palette entries") + trns_data = file.read(size) + for i in range(len(trns_data)): + if trns_data[i] == 0: + pal.make_transparent(i) + del trns_data + elif chunk == b"IDAT": + data.extend(file.read(size)) + elif chunk == b"IEND": + break + else: + file.seek(size, 1) # skip unknown chunks + file.seek(4, 1) # skip CRC + data_bytes = zlib.decompress(data) + unit = (1, 0, 3, 1, 2, 0, 4)[mode] + scanline = (width * depth * unit + 7) // 8 + if mode == 3: # indexed + bmp = bitmap(width, height, 1 << depth) + pixels_per_byte = 8 // depth + src = 1 + src_b = 1 + pixmask = (1 << depth) - 1 + for y in range(height): + for x in range(0, width, pixels_per_byte): + byte = data_bytes[src_b] + for pixel in range(pixels_per_byte): + bmp[x + pixel, y] = (byte >> ((pixels_per_byte - pixel - 1) * depth)) & pixmask + src_b += 1 + src += scanline + 1 + src_b = src + return bmp, pal + # RGB, RGBA or Grayscale + import displayio + + if depth != 8: + raise ValueError("Must be 8bit depth.") + pal = displayio.ColorConverter(input_colorspace=displayio.Colorspace.RGB888) + bmp = bitmap(width, height, 65536) + prev = bytearray(scanline) + line = bytearray(scanline) + for y in range(height): + src = y * (scanline + 1) + filter_ = data_bytes[src] + src += 1 + if filter_ == 0: + line[0:scanline] = data_bytes[src : src + scanline] + elif filter_ == 1: # sub + for i in range(scanline): + a = line[i - unit] if i >= unit else 0 + line[i] = (data_bytes[src] + a) & 0xFF + src += 1 + elif filter_ == 2: # up + for i in range(scanline): + b = prev[i] + line[i] = (data_bytes[src] + b) & 0xFF + src += 1 + elif filter_ == 3: # average + for i in range(scanline): + a = line[i - unit] if i >= unit else 0 + b = prev[i] + line[i] = (data_bytes[src] + ((a + b) >> 1)) & 0xFF + src += 1 + elif filter_ == 4: # paeth + for i in range(scanline): + a = line[i - unit] if i >= unit else 0 + b = prev[i] + c = prev[i - unit] if i >= unit else 0 + p = a + b - c + pa = abs(p - a) + pb = abs(p - b) + pc = abs(p - c) + if pa <= pb and pa <= pc: + p = a + elif pb <= pc: + p = b + else: + p = c + line[i] = (data_bytes[src] + p) & 0xFF + src += 1 + else: + raise ValueError("Wrong filter.") + prev, line = line, prev + if mode in (0, 4): # grayscale + for x in range(width): + c = line[x * unit] + bmp[x, y] = pal.convert((c << 16) | (c << 8) | c) + elif mode in {2, 6}: # rgb + for x in range(width): + bmp[x, y] = pal.convert( + (line[x * unit + 0] << 16) | (line[x * unit + 1] << 8) | line[x * unit + 2] + ) + else: + raise ValueError("Unsupported color mode.") + pal = displayio.ColorConverter(input_colorspace=displayio.Colorspace.RGB565) + return bmp, pal diff --git a/src/lib/adafruit_imageload/pnm/__init__.py b/src/lib/adafruit_imageload/pnm/__init__.py new file mode 100644 index 0000000..971da14 --- /dev/null +++ b/src/lib/adafruit_imageload/pnm/__init__.py @@ -0,0 +1,138 @@ +# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2022-2023 Matt Land +# SPDX-FileCopyrightText: Brooke Storm +# SPDX-FileCopyrightText: Sam McGahan +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.pnm` +==================================================== + +Load pixel values (indices or colors) into a bitmap and colors into a palette. + +* Author(s): Matt Land, Brooke Storm, Sam McGahan + +""" + +try: + from io import BufferedReader + from typing import ( + Callable, + Iterable, + Iterator, + List, + Optional, + Tuple, + Union, + ) + + from displayio import Bitmap, Palette + + from ..displayio_types import BitmapConstructor, PaletteConstructor +except ImportError: + pass + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + + +def load( # noqa: PLR0912 Too many branches + file: BufferedReader, + header: bytes, + *, + bitmap: Optional[BitmapConstructor] = None, + palette: Optional[PaletteConstructor] = None, +) -> Tuple[Optional[Bitmap], Optional[Palette]]: + """ + Scan for netpbm format info, skip over comments, and delegate to a submodule + to do the actual data loading. + Formats P1, P4 have two space padded pieces of information: width and height. + All other formats have three: width, height, and max color value. + This load function will move the file stream pointer to the start of data in all cases. + """ + magic_number = header[:2] + file.seek(2) + pnm_header = [] # type: List[int] + next_value = bytearray() + while True: + # We have all we need at length 3 for formats P2, P3, P5, P6 + if len(pnm_header) == 3: + if magic_number in [b"P2", b"P5"]: + from . import pgm + + return pgm.load( + file, + magic_number, + pnm_header, + bitmap=bitmap, + palette=palette, + ) + + if magic_number == b"P3": + from . import ppm_ascii + + return ppm_ascii.load( + file, + pnm_header[0], + pnm_header[1], + bitmap=bitmap, + palette=palette, + ) + + if magic_number == b"P6": + from . import ppm_binary + + return ppm_binary.load( + file, + pnm_header[0], + pnm_header[1], + bitmap=bitmap, + palette=palette, + ) + + if len(pnm_header) == 2 and magic_number in [b"P1", b"P4"]: + if not bitmap: + raise RuntimeError( + "A bitmap constructor is required for this type of pnm format file" + ) + bitmap_obj = bitmap(pnm_header[0], pnm_header[1], 1) + palette_obj = None + if palette: + palette_obj = palette(1) + palette_obj[0] = b"\xff\xff\xff" + if magic_number.startswith(b"P1"): + from . import pbm_ascii + + return pbm_ascii.load( + file, + pnm_header[0], + pnm_header[1], + bitmap=bitmap_obj, + palette=palette_obj, + ) + + from . import pbm_binary + + return pbm_binary.load( + file, + pnm_header[0], + pnm_header[1], + bitmap=bitmap_obj, + palette=palette_obj, + ) + + next_byte = file.read(1) + if next_byte == b"": + # mpy-cross does not support !r in f-string substitution, so ignore ruff rule + raise RuntimeError("Unsupported image format {!r}".format(magic_number)) # noqa: UP032, f-string + if next_byte == b"#": # comment found, seek until a newline or EOF is found + while file.read(1) not in [b"", b"\n"]: # EOF or NL + pass + elif not next_byte.isdigit(): # boundary found in header data + if next_value: + # pull values until space is found + pnm_header.append(int("".join(["%c" % char for char in next_value]))) + next_value = bytearray() # reset the byte array + else: + next_value += next_byte # push the digit into the byte array diff --git a/src/lib/adafruit_imageload/pnm/pbm_ascii.py b/src/lib/adafruit_imageload/pnm/pbm_ascii.py new file mode 100644 index 0000000..72eefd4 --- /dev/null +++ b/src/lib/adafruit_imageload/pnm/pbm_ascii.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2022-2023 Matt Land +# SPDX-FileCopyrightText: Brooke Storm +# SPDX-FileCopyrightText: Sam McGahan +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.pnm.pbm_ascii` +==================================================== + +Load pixel values (indices or colors) into a bitmap and for an ascii ppm, +return None for pallet. + +* Author(s): Matt Land, Brooke Storm, Sam McGahan + +""" + +try: + from io import BufferedReader + from typing import Optional, Tuple + + from displayio import Bitmap, Palette +except ImportError: + pass + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + + +def load( + file: BufferedReader, + width: int, + height: int, + bitmap: Bitmap, + palette: Optional[Palette] = None, +) -> Tuple[Bitmap, Optional[Palette]]: + """ + Load a P1 'PBM' ascii image into the displayio.Bitmap + """ + next_byte = b"1" # just to start the iterator + for y in range(height): + x = 0 + while next_byte: + next_byte = file.read(1) + if not next_byte.isdigit(): + continue + bitmap[x, y] = 1 if next_byte == b"1" else 0 + if x == width - 1: + break + x += 1 + return bitmap, palette diff --git a/src/lib/adafruit_imageload/pnm/pbm_binary.py b/src/lib/adafruit_imageload/pnm/pbm_binary.py new file mode 100644 index 0000000..1d52a8e --- /dev/null +++ b/src/lib/adafruit_imageload/pnm/pbm_binary.py @@ -0,0 +1,74 @@ +# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2022-2023 Matt Land +# SPDX-FileCopyrightText: Brooke Storm +# SPDX-FileCopyrightText: Sam McGahan +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.pnm.pbm_binary` +==================================================== + +Load pixel values (indices or colors) into a bitmap and for an ascii ppm, +return None for pallet. + +* Author(s): Matt Land, Brooke Storm, Sam McGahan + +""" + +try: + from io import BufferedReader + from typing import Iterator, Optional, Tuple + + from displayio import Bitmap, Palette +except ImportError: + pass + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + + +def load( + file: BufferedReader, + width: int, + height: int, + bitmap: Bitmap, + palette: Optional[Palette] = None, +) -> Tuple[Bitmap, Optional[Palette]]: + """ + Load a P4 'PBM' binary image into the Bitmap + """ + x = 0 + y = 0 + while True: + next_byte = file.read(1) + if not next_byte: + break # out of bits + for bit in iterbits(next_byte): + bitmap[x, y] = bit + x += 1 + if x > width - 1: + y += 1 + x = 0 + if y > height - 1: + break + return bitmap, palette + + +def iterbits(b: bytes) -> Iterator[int]: + """ + generator to iterate over the bits in a byte (character) + """ + in_char = reverse(int.from_bytes(b, "little")) + for i in range(8): + yield (in_char >> i) & 1 + + +def reverse(b: int) -> int: + """ + reverse bit order so the iterbits works + """ + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4 + b = (b & 0xCC) >> 2 | (b & 0x33) << 2 + b = (b & 0xAA) >> 1 | (b & 0x55) << 1 + return b diff --git a/src/lib/adafruit_imageload/pnm/pgm/__init__.py b/src/lib/adafruit_imageload/pnm/pgm/__init__.py new file mode 100644 index 0000000..8acefb5 --- /dev/null +++ b/src/lib/adafruit_imageload/pnm/pgm/__init__.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2022-2023 Matt Land +# SPDX-FileCopyrightText: Brooke Storm +# SPDX-FileCopyrightText: Sam McGahan +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.pnm.pgm` +==================================================== + +Load pixel values (indices or colors) into a bitmap and colors into a palette. + +* Author(s): Matt Land, Brooke Storm, Sam McGahan + +""" + +try: + from io import BufferedReader + from typing import List, Optional, Set, Tuple + + from displayio import Bitmap, Palette + + from ...displayio_types import BitmapConstructor, PaletteConstructor +except ImportError: + pass + + +def load( + file: BufferedReader, + magic_number: bytes, + header: List[int], + *, + bitmap: Optional[BitmapConstructor] = None, + palette: Optional[PaletteConstructor] = None, +) -> Tuple[Optional[Bitmap], Optional[Palette]]: + """ + Perform the load of Netpbm greyscale images (P2, P5) + """ + if header[2] > 256: + raise NotImplementedError("16 bit files are not supported") + width = header[0] + height = header[1] + + if magic_number == b"P2": # To handle ascii PGM files. + from . import ascii as pgm_ascii + + return pgm_ascii.load(file, width, height, bitmap=bitmap, palette=palette) + + if magic_number == b"P5": # To handle binary PGM files. + from . import binary + + return binary.load(file, width, height, bitmap=bitmap, palette=palette) + + raise NotImplementedError("Was not able to send image") diff --git a/src/lib/adafruit_imageload/pnm/pgm/ascii.py b/src/lib/adafruit_imageload/pnm/pgm/ascii.py new file mode 100644 index 0000000..7c597ca --- /dev/null +++ b/src/lib/adafruit_imageload/pnm/pgm/ascii.py @@ -0,0 +1,79 @@ +# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2022-2023 Matt Land +# SPDX-FileCopyrightText: Brooke Storm +# SPDX-FileCopyrightText: Sam McGahan +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.pnm.pgm.ascii` +==================================================== + +Load pixel values (indices or colors) into a bitmap and colors into a palette. + +* Author(s): Matt Land, Brooke Storm, Sam McGahan + +""" + +try: + from io import BufferedReader + from typing import Optional, Set, Tuple + + from displayio import Bitmap, Palette + + from ...displayio_types import BitmapConstructor, PaletteConstructor +except ImportError: + pass + + +def load( + file: BufferedReader, + width: int, + height: int, + bitmap: Optional[BitmapConstructor] = None, + palette: Optional[PaletteConstructor] = None, +) -> Tuple[Optional[Bitmap], Optional[Palette]]: + """ + Load a PGM ascii file (P2) + """ + data_start = file.tell() # keep this so we can rewind + _palette_colors = set() + pixel = bytearray() + # build a set of all colors present in the file, so palette and bitmap can be constructed + while True: + byte = file.read(1) + if byte == b"": + break + if not byte.isdigit(): + int_pixel = int("".join(["%c" % char for char in pixel])) + _palette_colors.add(int_pixel) + pixel = bytearray() + pixel += byte + palette_obj = None + if palette: + palette_obj = build_palette(palette, _palette_colors) + bitmap_obj = None + if bitmap: + bitmap_obj = bitmap(width, height, len(_palette_colors)) + file.seek(data_start) + for y in range(height): + for x in range(width): + pixel = bytearray() + while True: + byte = file.read(1) + if not byte.isdigit(): + break + pixel += byte + int_pixel = int("".join(["%c" % char for char in pixel])) + bitmap_obj[x, y] = list(_palette_colors).index(int_pixel) + return bitmap_obj, palette_obj + + +def build_palette(palette_class: PaletteConstructor, palette_colors: Set[int]) -> Palette: + """ + construct the Palette, and populate it with the set of palette_colors + """ + palette = palette_class(len(palette_colors)) + for counter, color in enumerate(palette_colors): + palette[counter] = bytes([color, color, color]) + return palette diff --git a/src/lib/adafruit_imageload/pnm/pgm/binary.py b/src/lib/adafruit_imageload/pnm/pgm/binary.py new file mode 100644 index 0000000..700b563 --- /dev/null +++ b/src/lib/adafruit_imageload/pnm/pgm/binary.py @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2022-2023 Matt Land +# SPDX-FileCopyrightText: Brooke Storm +# SPDX-FileCopyrightText: Sam McGahan +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.pnm.pgm.binary` +==================================================== + +Load pixel values (indices or colors) into a bitmap and colors into a palette. + +* Author(s): Matt Land, Brooke Storm, Sam McGahan + +""" + +try: + from io import BufferedReader + from typing import Optional, Set, Tuple + + from displayio import Bitmap, Palette + + from ...displayio_types import BitmapConstructor, PaletteConstructor +except ImportError: + pass + + +def load( + file: BufferedReader, + width: int, + height: int, + bitmap: Optional[BitmapConstructor] = None, + palette: Optional[PaletteConstructor] = None, +) -> Tuple[Optional[Bitmap], Optional[Palette]]: + """ + Load a P5 format file (binary), handle PGM (greyscale) + """ + palette_colors = set() # type: Set[int] + data_start = file.tell() + for y in range(height): + data_line = iter(bytes(file.read(width))) + for pixel in data_line: + palette_colors.add(pixel) + + palette_obj = None + if palette: + palette_obj = build_palette(palette, palette_colors) + bitmap_obj = None + if bitmap: + bitmap_obj = bitmap(width, height, len(palette_colors)) + file.seek(data_start) + for y in range(height): + data_line = iter(bytes(file.read(width))) + for x, pixel in enumerate(data_line): + bitmap_obj[x, y] = list(palette_colors).index(pixel) + return bitmap_obj, palette_obj + + +def build_palette(palette_class: PaletteConstructor, palette_colors: Set[int]) -> Palette: + """ + construct the Palette, and populate it with the set of palette_colors + """ + _palette = palette_class(len(palette_colors)) + for counter, color in enumerate(palette_colors): + _palette[counter] = bytes([color, color, color]) + return _palette diff --git a/src/lib/adafruit_imageload/pnm/ppm_ascii.py b/src/lib/adafruit_imageload/pnm/ppm_ascii.py new file mode 100644 index 0000000..ac8ea8a --- /dev/null +++ b/src/lib/adafruit_imageload/pnm/ppm_ascii.py @@ -0,0 +1,98 @@ +# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2022-2023 Matt Land +# SPDX-FileCopyrightText: Brooke Storm +# SPDX-FileCopyrightText: Sam McGahan +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.pnm.ppm_ascii` +==================================================== + +Load pixel values (indices or colors) into a bitmap and for an ascii ppm, +return None for pallet. + +* Author(s): Matt Land, Brooke Storm, Sam McGahan + +""" + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + +try: + from io import BufferedReader + from typing import ( + Iterator, + List, + Optional, + Set, + Tuple, + ) + + from displayio import Bitmap, Palette + + from ..displayio_types import BitmapConstructor, PaletteConstructor +except ImportError: + pass + + +def load( + file: BufferedReader, + width: int, + height: int, + bitmap: Optional[BitmapConstructor] = None, + palette: Optional[PaletteConstructor] = None, +) -> Tuple[Optional[Bitmap], Optional[Palette]]: + """ + :param stream file: infile with the position set at start of data + :param int width: + :param int height: + :param int max_colors: color space of file + :param bitmap: displayio.Bitmap class + :param palette: displayio.Palette class + :return tuple: + """ + palette_colors = set() # type: Set[bytes] + data_start = file.tell() + for triplet in read_three_colors(file): + palette_colors.add(triplet) + + palette_obj = None + if palette: + palette_obj = palette(len(palette_colors)) + for counter, color in enumerate(palette_colors): + palette_obj[counter] = color + bitmap_obj = None + if bitmap: + file.seek(data_start) + bitmap_obj = bitmap(width, height, len(palette_colors)) + for y in range(height): + for x in range(width): + for color in read_three_colors(file): + bitmap_obj[x, y] = list(palette_colors).index(color) + break # exit the inner generator + return bitmap_obj, palette_obj + + +def read_three_colors(file: BufferedReader) -> Iterator[bytes]: + """ + Generator to read integer values from file, in groups of three. + Each value can be len 1-3, for values 0 - 255, space padded. + :return Iterator[bytes]: + """ + triplet = [] # type: List[int] + color = bytearray() + while True: + this_byte = file.read(1) + if this_byte.isdigit(): + color += this_byte + # not a digit means we completed one number (found a space separator or EOF) + elif color or (triplet and this_byte == b""): + triplet.append(int("".join(["%c" % char for char in color]))) + color = bytearray() + if len(triplet) == 3: # completed one pixel + yield bytes(tuple(triplet)) + triplet = [] + # short circuit must be after all other cases, so we yield the last pixel before returning + if this_byte == b"": + return diff --git a/src/lib/adafruit_imageload/pnm/ppm_binary.py b/src/lib/adafruit_imageload/pnm/ppm_binary.py new file mode 100644 index 0000000..cada568 --- /dev/null +++ b/src/lib/adafruit_imageload/pnm/ppm_binary.py @@ -0,0 +1,74 @@ +# SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2022-2023 Matt Land +# SPDX-FileCopyrightText: Brooke Storm +# SPDX-FileCopyrightText: Sam McGahan +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.pnm.ppm_binary` +==================================================== + +Load pixel values (indices or colors) into a bitmap and for a binary ppm, +return None for pallet. + +* Author(s): Matt Land, Brooke Storm, Sam McGahan + +""" + +try: + from io import BufferedReader + from typing import Optional, Set, Tuple + + from displayio import Bitmap, Palette + + from ..displayio_types import BitmapConstructor, PaletteConstructor +except ImportError: + pass + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + + +def load( + file: BufferedReader, + width: int, + height: int, + bitmap: Optional[BitmapConstructor] = None, + palette: Optional[PaletteConstructor] = None, +) -> Tuple[Optional[Bitmap], Optional[Palette]]: + """ + Load pixel values (indices or colors) into a bitmap and for a binary + ppm, return None for pallet. + """ + + data_start = file.tell() + palette_colors = set() # type: Set[Tuple[int, int, int]] + line_size = width * 3 + + for y in range(height): + data_line = iter(bytes(file.read(line_size))) + for red in data_line: + # red, green, blue + palette_colors.add((red, next(data_line), next(data_line))) + + palette_obj = None + if palette: + palette_obj = palette(len(palette_colors)) + for counter, color in enumerate(palette_colors): + palette_obj[counter] = bytes(color) + bitmap_obj = None + if bitmap: + bitmap_obj = bitmap(width, height, len(palette_colors)) + file.seek(data_start) + for y in range(height): + x = 0 + data_line = iter(bytes(file.read(line_size))) + for red in data_line: + # red, green, blue + bitmap_obj[x, y] = list(palette_colors).index( + (red, next(data_line), next(data_line)) + ) + x += 1 + + return bitmap_obj, palette_obj diff --git a/src/lib/adafruit_imageload/tilegrid_inflator.py b/src/lib/adafruit_imageload/tilegrid_inflator.py new file mode 100644 index 0000000..8df1a22 --- /dev/null +++ b/src/lib/adafruit_imageload/tilegrid_inflator.py @@ -0,0 +1,109 @@ +# SPDX-FileCopyrightText: 2022 Tim Cocks for Adafruit Industries +# SPDX-FileCopyrightText: 2022-2023 Matt Land +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.tilegrid_inflator` +==================================================== + +Use a 3x3 spritesheet to inflate a larger grid of tiles, duplicating the center rows and +columns as many times as needed to reach a target size. + +* Author(s): Tim Cocks, Matt Land + +""" + +import displayio + +import adafruit_imageload + +try: + from typing import List, Optional, Tuple, Union + + from displayio import Bitmap, OnDiskBitmap, Palette, TileGrid +except ImportError: + pass + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + + +def inflate_tilegrid( # noqa: PLR0913, PLR0912, Too many arguments in function definition, Too many branches + bmp_path: Optional[str] = None, + target_size: Tuple[int, int] = (3, 3), + tile_size: Optional[List[int]] = None, + transparent_index: Optional[Union[tuple, int]] = None, + bmp_obj: Optional[OnDiskBitmap] = None, + bmp_palette: Optional[Palette] = None, +) -> TileGrid: + """ + inflate a TileGrid of ``target_size`` in tiles from a 3x3 spritesheet by duplicating + the center rows and columns. + + :param Optional[str] bmp_path: filepath to the 3x3 spritesheet bitmap file + :param tuple[int, int] target_size: desired size in tiles (target_width, target_height) + :param Optional[List[int]] tile_size: size of the tiles in the 3x3 spritesheet. If + None is used it will equally divide the width and height of the Bitmap by 3. + :param Optional[Union[tuple, int]] transparent_index: a single index within the palette to + make transparent, or a tuple of multiple indexes to make transparent + :param Optional[OnDiskBitmap] bmp_obj: Already loaded 3x3 spritesheet in an OnDiskBitmap + :param Optional[Palette] bmp_palette: Already loaded spritesheet Palette + """ + + if bmp_path is None and (bmp_obj is None and bmp_palette is None): + raise AttributeError("Must pass either bmp_path or bmp_obj and bmp_palette") + + image: Bitmap + palette: Palette + if bmp_path is not None: + image, palette = adafruit_imageload.load(bmp_path) # type: ignore[assignment] + else: + image = bmp_obj # type: ignore[assignment] + palette = bmp_palette # type: ignore[assignment] + + if transparent_index is not None: + if isinstance(transparent_index, tuple): + for index in transparent_index: + palette.make_transparent(index) + elif isinstance(transparent_index, int): + palette.make_transparent(transparent_index) + + if tile_size is None: + tile_width = image.width // 3 + tile_height = image.height // 3 + else: + tile_width = tile_size[0] + tile_height = tile_size[1] + + target_width = target_size[0] + target_height = target_size[1] + + tile_grid = displayio.TileGrid( + image, + pixel_shader=palette, + height=target_height, + width=target_width, + tile_width=tile_width, + tile_height=tile_height, + ) + + # corners + tile_grid[0, 0] = 0 # upper left + tile_grid[tile_grid.width - 1, 0] = 2 # upper right + tile_grid[0, tile_grid.height - 1] = 6 # lower left + tile_grid[tile_grid.width - 1, tile_grid.height - 1] = 8 # lower right + + for x in range(target_size[0] - 2): + tile_grid[x + 1, 0] = 1 + tile_grid[x + 1, tile_grid.height - 1] = 7 + + for y in range(target_size[1] - 2): + tile_grid[0, y + 1] = 3 + tile_grid[tile_grid.width - 1, y + 1] = 5 + + for y in range(target_size[1] - 2): + for x in range(target_size[0] - 2): + tile_grid[x + 1, y + 1] = 4 + + return tile_grid diff --git a/src/lib/adafruit_irremote.mpy b/src/lib/adafruit_irremote.mpy new file mode 100644 index 0000000000000000000000000000000000000000..4f1634a9635f3dc09084a3a11bdfc4c3184ee1ab GIT binary patch literal 2977 zcmZuxTTmO<89pl{5aQxxCDt;=c)hwHkRpT-#7&$SR%>yyL6Rk5#ghtM7ElqpsbWEfs$r2pU0y4?^+`6Jet&tcp$*U|RMnNTsg!by4kFc+K5CG6T56@k{$a0L z*==|Bst%2DJi)MBZhD50Q&JtJIDUp;zF^ocE|DP0u8Bl)Y6@0RrSnP?oh1?+0k`QL zd}N$W%@cFc%q(Fhvgv4?A!ed%0?sH<9jTbWXJ?ZMM%L<*b%46cVegg>$L~`%4>H+* z)@<{Tp9bARw=pqG%o5iQU%SxR5C4<1#C6y8kbo*Wk)u_u3S1Eud`FeIQb1J@scu!_ zDha8nBM znIr3VOu9@F`O$1LBXo9A`h|tf(wa^UUVonHoaR|e^KH-aVQ>b zmxQ1}xwkxV@r|ceqE&luDC)B?{Y%I!@8;hYSA#+b9^!?dxN|Y&-wMMYMN!C#d!(p4 zvMgE^eMp$PwNQ-T3KY>REf!&Fa?(6SboTWIJss95D@|Eu%$^Rj$87aYb2cm41sQi?UC@OQMFV4D1c zw;;-BHTmb0W$+?-l`7;b;BQZrK@X;d?~Tj;zJ5vjuv10!DB9}EBO91*$%QboB>!*W zHBB#UQj~AAATH$aFhAQG~+Ozs#?FluBxuLGat~@9`p(^S%2i4bQc}1fBA4&dIC3i#Z0SUbhn;cvh z;o(v|#m*dLpP3rzq3?()M6PFf2F4tU=P&NSp5K!TdgoooJ^sSo5S6<9PIUXd$Sa8| zX=&aErwR*#6_LyL)$Zcky}woF?6?%;{@um5LGfql3f-eeq#MrAPZvYfaSX!-U`x=CY8K_N1F=wVR|!b0^GgWYhAS^BjsZIGp}pcKlw_hsMw zKUw~=Ut{)ogm0~&7#TgWVFL2FFG)|=$V_G4-Q*HwF$uupKsGr^*Q|dNCqGDf$-9F7`k%*b-@?Eme9B1H-S)-Ag*bQ*H5sM zCOKyuKkWqYYlPq}QtpPjPR02i1#UlY0}_w6#r){;n4u0Oq~k=arwVuY;>vVOy?)Sg zLrq|MX??CBx z9VbrQ=ojjUq{TuOboS!XN?+CXGCf{`zD6XgRmqAf#i{9f2AUclY`f zkKry#qR42YL-FJ(q}PLM>mkY^>wQmq;JvmGg3BvuJrkZ|{V-5uG~`gcc={E!B}_b7 zx)&7JfBu*7iVhUCP9eBy$xVPE_|jFYv^ literal 0 HcmV?d00001 diff --git a/src/lib/adafruit_ntp.mpy b/src/lib/adafruit_ntp.mpy new file mode 100644 index 0000000000000000000000000000000000000000..95130dc1f9f0aca3e0dc77ef5820dbd4088df172 GIT binary patch literal 1245 zcmaJ(#@Z(^T(rIV0Wf|OJ$s?^Z zp6?jy@H^MYC>%b^m+}X*JT8{QKhPYhQG1yNeBtyGH@;Amw?w$^gD_Ux@($_ z0FB;;TDfc}2m}mGeoH~n&eG}#oRe%7Yp3G}IE(0Ie(X z9avGZ%hgp|04(cT6*vd40&jajuEA^;P<9io!P1+WT-a>6-a3-)emMy7XpM@dLI@}b zb)}|ZFNEg~^NVn0A^R$vU0O`P0=T7_^o`j(T+Y5W3t|&jsG6?T6di4WCc~acX-&I9 zG+uU93wJ@+)CNK|V=5L~ub_>3QINH2taJ3Sna&+ZS9BTQg{3XDft$d*0)_*(Cmt75 zlc`in6ubIFp_3=r9D;psG<_YTlJUvpr1WBZQrhuSJh2uu{ljyckxZmy=K4vF#H)jI zn3Qvj=whnAZa`Wyj0%xbFtcazzI}`DePHoa&f@)aJR~Lwi>K+tLFlEY__cS; zLyKo0S^PQE;yL{F<>dGI`_vRKn)7QTC^$S6OOB32E7xz1&nJxW`SI#>@J);H=l;y( zEyl;bU@^TGLrJ8$va^C&KC&3vyw$jR{q|!MyL-19f87p=zoo@h8+{%xOuxMZB=VZ@ z^*(#SVpxe-r5)^m1ism4FXALzULsfb*&z0x{rfie`WrOK4pJc;rNT|Ai7CIMlg)?! zSj@R3`TfD8(7iwY{s25Z#41|INDKKPMvCMQKRy@wxE1>8$tU#KQ9J3=whcRu4}bpb zH2(Q>@n61paP;ND(P8uW*RL#wIi=%r^1aj`JtUgj)6sHJKT%^beZObsES8r*Tc5>n ehbLLbk1=L=Pa@=I%1@1m=DTab=-mJ1$^QbB3W2-; literal 0 HcmV?d00001 diff --git a/src/lib/adafruit_register/__init__.py b/src/lib/adafruit_register/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/adafruit_register/i2c_bcd_alarm.py b/src/lib/adafruit_register/i2c_bcd_alarm.py new file mode 100644 index 0000000..178d8e1 --- /dev/null +++ b/src/lib/adafruit_register/i2c_bcd_alarm.py @@ -0,0 +1,202 @@ +# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT +# pylint: disable=too-few-public-methods +# pylint: disable=too-many-branches + +""" +`adafruit_register.i2c_bcd_alarm` +==================================================== + +Binary Coded Decimal alarm register + +* Author(s): Scott Shawcroft +""" + +__version__ = "1.10.1" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" + +import time + +try: + from typing import Optional, Type, Tuple + from typing_extensions import Literal + from circuitpython_typing.device_drivers import I2CDeviceDriver + + FREQUENCY_T = Literal[ + "monthly", "weekly", "daily", "hourly", "minutely", "secondly" + ] +except ImportError: + pass + + +def _bcd2bin(value: int) -> int: + """Convert binary coded decimal to Binary + + :param value: the BCD value to convert to binary (required, no default) + """ + return value - 6 * (value >> 4) + + +def _bin2bcd(value: int) -> int: + """Convert a binary value to binary coded decimal. + + :param value: the binary value to convert to BCD. (required, no default) + """ + return value + 6 * (value // 10) + + +ALARM_COMPONENT_DISABLED = 0x80 +FREQUENCY = ["secondly", "minutely", "hourly", "daily", "weekly", "monthly"] + + +class BCDAlarmTimeRegister: + """ + Alarm date and time register using binary coded decimal structure. + + The byte order of the registers must* be: [second], minute, hour, day, + weekday. Each byte must also have a high enable bit where 1 is disabled and + 0 is enabled. + + * If weekday_shared is True, then weekday and day share a register. + * If has_seconds is True, then there is a seconds register. + + Values are a tuple of (`time.struct_time`, `str`) where the struct represents + a date and time that would alarm. The string is the frequency: + + * "secondly", once a second (only if alarm has_seconds) + * "minutely", once a minute when seconds match (if alarm doesn't seconds then when seconds = 0) + * "hourly", once an hour when ``tm_min`` and ``tm_sec`` match + * "daily", once a day when ``tm_hour``, ``tm_min`` and ``tm_sec`` match + * "weekly", once a week when ``tm_wday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match + * "monthly", once a month when ``tm_mday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match + + :param int register_address: The register address to start the read + :param bool has_seconds: True if the alarm can happen minutely. + :param bool weekday_shared: True if weekday and day share the same register + :param int weekday_start: 0 or 1 depending on the RTC's representation of the first day of the + week (Monday) + """ + + # Defaults are based on alarm1 of the DS3231. + def __init__( + self, + register_address: int, + has_seconds: bool = True, + weekday_shared: bool = True, + weekday_start: Literal[0, 1] = 1, + ) -> None: + buffer_size = 5 + if weekday_shared: + buffer_size -= 1 + if has_seconds: + buffer_size += 1 + self.has_seconds = has_seconds + self.buffer = bytearray(buffer_size) + self.buffer[0] = register_address + self.weekday_shared = weekday_shared + self.weekday_start = weekday_start + + def __get__( + self, + obj: Optional[I2CDeviceDriver], + objtype: Optional[Type[I2CDeviceDriver]] = None, + ) -> Tuple[time.struct_time, FREQUENCY_T]: + # Read the alarm register. + with obj.i2c_device as i2c: + i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) + + frequency = None + i = 1 + seconds = 0 + if self.has_seconds: + if (self.buffer[1] & 0x80) != 0: + frequency = "secondly" + else: + frequency = "minutely" + seconds = _bcd2bin(self.buffer[1] & 0x7F) + i = 2 + else: + frequency = "minutely" + seconds = _bcd2bin(self.buffer[i] & 0x7F) + minute = 0 + if (self.buffer[i] & 0x80) == 0: + frequency = "hourly" + minute = _bcd2bin(self.buffer[i] & 0x7F) + + hour = 0 + if (self.buffer[i + 1] & 0x80) == 0: + frequency = "daily" + hour = _bcd2bin(self.buffer[i + 1] & 0x7F) + + mday = None + wday = None + if (self.buffer[i + 2] & 0x80) == 0: + # day of the month + if not self.weekday_shared or (self.buffer[i + 2] & 0x40) == 0: + frequency = "monthly" + mday = _bcd2bin(self.buffer[i + 2] & 0x3F) + else: # weekday + frequency = "weekly" + wday = _bcd2bin(self.buffer[i + 2] & 0x3F) - self.weekday_start + + # weekday + if not self.weekday_shared and (self.buffer[i + 3] & 0x80) == 0: + frequency = "monthly" + mday = _bcd2bin(self.buffer[i + 3] & 0x7F) + + if mday is not None: + wday = (mday - 2) % 7 + elif wday is not None: + mday = wday + 2 + else: + # Jan 1, 2017 was a Sunday (6) + wday = 6 + mday = 1 + + return ( + time.struct_time((2017, 1, mday, hour, minute, seconds, wday, mday, -1)), + frequency, + ) + + def __set__( + self, obj: I2CDeviceDriver, value: Tuple[time.struct_time, FREQUENCY_T] + ) -> None: + if len(value) != 2: + raise ValueError("Value must be sequence of length two") + # Turn all components off by default. + for i in range(len(self.buffer) - 1): + self.buffer[i + 1] = ALARM_COMPONENT_DISABLED + frequency_name = value[1] + error_message = "%s is not a supported frequency" % frequency_name + if frequency_name not in FREQUENCY: + raise ValueError(error_message) + + frequency = FREQUENCY.index(frequency_name) + if frequency < 1 and not self.has_seconds: + raise ValueError(error_message) + + # i is the index of the minute byte + i = 2 if self.has_seconds else 1 + + if frequency > 0 and self.has_seconds: # minutely at least + self.buffer[1] = _bin2bcd(value[0].tm_sec) + + if frequency > 1: # hourly at least + self.buffer[i] = _bin2bcd(value[0].tm_min) + + if frequency > 2: # daily at least + self.buffer[i + 1] = _bin2bcd(value[0].tm_hour) + + if value[1] == "weekly": + if self.weekday_shared: + self.buffer[i + 2] = ( + _bin2bcd(value[0].tm_wday + self.weekday_start) | 0x40 + ) + else: + self.buffer[i + 3] = _bin2bcd(value[0].tm_wday + self.weekday_start) + elif value[1] == "monthly": + self.buffer[i + 2] = _bin2bcd(value[0].tm_mday) + + with obj.i2c_device: + obj.i2c_device.write(self.buffer) diff --git a/src/lib/adafruit_register/i2c_bcd_datetime.py b/src/lib/adafruit_register/i2c_bcd_datetime.py new file mode 100644 index 0000000..0a23ffe --- /dev/null +++ b/src/lib/adafruit_register/i2c_bcd_datetime.py @@ -0,0 +1,114 @@ +# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT +# pylint: disable=too-few-public-methods + +""" +`adafruit_register.i2c_bcd_datetime` +==================================================== + +Binary Coded Decimal date and time register + +* Author(s): Scott Shawcroft +""" + +__version__ = "1.10.1" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" + +import time + +try: + from typing import Optional, Type + from typing_extensions import Literal + from circuitpython_typing.device_drivers import I2CDeviceDriver +except ImportError: + pass + + +def _bcd2bin(value: int) -> int: + """Convert binary coded decimal to Binary + + :param value: the BCD value to convert to binary (required, no default) + """ + return value - 6 * (value >> 4) + + +def _bin2bcd(value: int) -> int: + """Convert a binary value to binary coded decimal. + + :param value: the binary value to convert to BCD. (required, no default) + """ + return value + 6 * (value // 10) + + +class BCDDateTimeRegister: + """ + Date and time register using binary coded decimal structure. + + The byte order of the register must* be: second, minute, hour, weekday, day (1-31), month, year + (in years after 2000). + + * Setting weekday_first=False will flip the weekday/day order so that day comes first. + + Values are `time.struct_time` + + :param int register_address: The register address to start the read + :param bool weekday_first: True if weekday is in a lower register than the day of the month + (1-31) + :param int weekday_start: 0 or 1 depending on the RTC's representation of the first day of the + week + """ + + def __init__( + self, + register_address: int, + weekday_first: bool = True, + weekday_start: Literal[0, 1] = 1, + ) -> None: + self.buffer = bytearray(8) + self.buffer[0] = register_address + if weekday_first: + self.weekday_offset = 0 + else: + self.weekday_offset = 1 + self.weekday_start = weekday_start + # Masking value list n/a sec min hr day wkday mon year + self.mask_datetime = b"\xFF\x7F\x7F\x3F\x3F\x07\x1F\xFF" + + def __get__( + self, + obj: Optional[I2CDeviceDriver], + objtype: Optional[Type[I2CDeviceDriver]] = None, + ) -> time.struct_time: + # Read and return the date and time. + with obj.i2c_device as i2c: + i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) + return time.struct_time( + ( + _bcd2bin(self.buffer[7] & self.mask_datetime[7]) + 2000, + _bcd2bin(self.buffer[6] & self.mask_datetime[6]), + _bcd2bin(self.buffer[5 - self.weekday_offset] & self.mask_datetime[4]), + _bcd2bin(self.buffer[3] & self.mask_datetime[3]), + _bcd2bin(self.buffer[2] & self.mask_datetime[2]), + _bcd2bin(self.buffer[1] & self.mask_datetime[1]), + _bcd2bin( + (self.buffer[4 + self.weekday_offset] & self.mask_datetime[5]) + - self.weekday_start + ), + -1, + -1, + ) + ) + + def __set__(self, obj: I2CDeviceDriver, value: time.struct_time) -> None: + self.buffer[1] = _bin2bcd(value.tm_sec) & 0x7F # format conversions + self.buffer[2] = _bin2bcd(value.tm_min) + self.buffer[3] = _bin2bcd(value.tm_hour) + self.buffer[4 + self.weekday_offset] = _bin2bcd( + value.tm_wday + self.weekday_start + ) + self.buffer[5 - self.weekday_offset] = _bin2bcd(value.tm_mday) + self.buffer[6] = _bin2bcd(value.tm_mon) + self.buffer[7] = _bin2bcd(value.tm_year - 2000) + with obj.i2c_device: + obj.i2c_device.write(self.buffer) diff --git a/src/lib/adafruit_register/i2c_bit.py b/src/lib/adafruit_register/i2c_bit.py new file mode 100644 index 0000000..e518957 --- /dev/null +++ b/src/lib/adafruit_register/i2c_bit.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT +# pylint: disable=too-few-public-methods + +""" +`adafruit_register.i2c_bit` +==================================================== + +Single bit registers + +* Author(s): Scott Shawcroft +""" + +__version__ = "1.10.1" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" + +try: + from typing import Optional, Type, NoReturn + from circuitpython_typing.device_drivers import I2CDeviceDriver +except ImportError: + pass + + +class RWBit: + """ + Single bit register that is readable and writeable. + + Values are `bool` + + :param int register_address: The register address to read the bit from + :param int bit: The bit index within the byte at ``register_address`` + :param int register_width: The number of bytes in the register. Defaults to 1. + :param bool lsb_first: Is the first byte we read from I2C the LSB? Defaults to true + + """ + + def __init__( + self, + register_address: int, + bit: int, + register_width: int = 1, + lsb_first: bool = True, + ) -> None: + self.bit_mask = 1 << (bit % 8) # the bitmask *within* the byte! + self.buffer = bytearray(1 + register_width) + self.buffer[0] = register_address + if lsb_first: + self.byte = bit // 8 + 1 # the byte number within the buffer + else: + self.byte = register_width - (bit // 8) # the byte number within the buffer + + def __get__( + self, + obj: Optional[I2CDeviceDriver], + objtype: Optional[Type[I2CDeviceDriver]] = None, + ) -> bool: + with obj.i2c_device as i2c: + i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) + return bool(self.buffer[self.byte] & self.bit_mask) + + def __set__(self, obj: I2CDeviceDriver, value: bool) -> None: + with obj.i2c_device as i2c: + i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) + if value: + self.buffer[self.byte] |= self.bit_mask + else: + self.buffer[self.byte] &= ~self.bit_mask + i2c.write(self.buffer) + + +class ROBit(RWBit): + """Single bit register that is read only. Subclass of `RWBit`. + + Values are `bool` + + :param int register_address: The register address to read the bit from + :param type bit: The bit index within the byte at ``register_address`` + :param int register_width: The number of bytes in the register. Defaults to 1. + + """ + + def __set__(self, obj: I2CDeviceDriver, value: bool) -> NoReturn: + raise AttributeError() diff --git a/src/lib/adafruit_register/i2c_bits.py b/src/lib/adafruit_register/i2c_bits.py new file mode 100644 index 0000000..08bf5be --- /dev/null +++ b/src/lib/adafruit_register/i2c_bits.py @@ -0,0 +1,114 @@ +# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT +# pylint: disable=too-few-public-methods + +""" +`adafruit_register.i2c_bits` +==================================================== + +Multi bit registers + +* Author(s): Scott Shawcroft +""" + +__version__ = "1.10.1" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" + +try: + from typing import Optional, Type, NoReturn + from circuitpython_typing.device_drivers import I2CDeviceDriver +except ImportError: + pass + + +class RWBits: + """ + Multibit register (less than a full byte) that is readable and writeable. + This must be within a byte register. + + Values are `int` between 0 and 2 ** ``num_bits`` - 1. + + :param int num_bits: The number of bits in the field. + :param int register_address: The register address to read the bit from + :param int lowest_bit: The lowest bits index within the byte at ``register_address`` + :param int register_width: The number of bytes in the register. Defaults to 1. + :param bool lsb_first: Is the first byte we read from I2C the LSB? Defaults to true + :param bool signed: If True, the value is a "two's complement" signed value. + If False, it is unsigned. + """ + + def __init__( # pylint: disable=too-many-arguments + self, + num_bits: int, + register_address: int, + lowest_bit: int, + register_width: int = 1, + lsb_first: bool = True, + signed: bool = False, + ) -> None: + self.bit_mask = ((1 << num_bits) - 1) << lowest_bit + # print("bitmask: ",hex(self.bit_mask)) + if self.bit_mask >= 1 << (register_width * 8): + raise ValueError("Cannot have more bits than register size") + self.lowest_bit = lowest_bit + self.buffer = bytearray(1 + register_width) + self.buffer[0] = register_address + self.lsb_first = lsb_first + self.sign_bit = (1 << (num_bits - 1)) if signed else 0 + + def __get__( + self, + obj: Optional[I2CDeviceDriver], + objtype: Optional[Type[I2CDeviceDriver]] = None, + ) -> int: + with obj.i2c_device as i2c: + i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) + # read the number of bytes into a single variable + reg = 0 + order = range(len(self.buffer) - 1, 0, -1) + if not self.lsb_first: + order = reversed(order) + for i in order: + reg = (reg << 8) | self.buffer[i] + reg = (reg & self.bit_mask) >> self.lowest_bit + # If the value is signed and negative, convert it + if reg & self.sign_bit: + reg -= 2 * self.sign_bit + return reg + + def __set__(self, obj: I2CDeviceDriver, value: int) -> None: + value <<= self.lowest_bit # shift the value over to the right spot + with obj.i2c_device as i2c: + i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) + reg = 0 + order = range(len(self.buffer) - 1, 0, -1) + if not self.lsb_first: + order = range(1, len(self.buffer)) + for i in order: + reg = (reg << 8) | self.buffer[i] + # print("old reg: ", hex(reg)) + reg &= ~self.bit_mask # mask off the bits we're about to change + reg |= value # then or in our new value + # print("new reg: ", hex(reg)) + for i in reversed(order): + self.buffer[i] = reg & 0xFF + reg >>= 8 + i2c.write(self.buffer) + + +class ROBits(RWBits): + """ + Multibit register (less than a full byte) that is read-only. This must be + within a byte register. + + Values are `int` between 0 and 2 ** ``num_bits`` - 1. + + :param int num_bits: The number of bits in the field. + :param int register_address: The register address to read the bit from + :param type lowest_bit: The lowest bits index within the byte at ``register_address`` + :param int register_width: The number of bytes in the register. Defaults to 1. + """ + + def __set__(self, obj: I2CDeviceDriver, value: int) -> NoReturn: + raise AttributeError() diff --git a/src/lib/adafruit_register/i2c_struct.py b/src/lib/adafruit_register/i2c_struct.py new file mode 100644 index 0000000..a097e3c --- /dev/null +++ b/src/lib/adafruit_register/i2c_struct.py @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT +# pylint: disable=too-few-public-methods + +""" +`adafruit_register.i2c_struct` +==================================================== + +Generic structured registers based on `struct` + +* Author(s): Scott Shawcroft +""" + +__version__ = "1.10.1" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" + +import struct + +try: + from typing import Optional, Type, Tuple, Any, NoReturn + from circuitpython_typing.device_drivers import I2CDeviceDriver +except ImportError: + pass + + +class Struct: + """ + Arbitrary structure register that is readable and writeable. + + Values are tuples that map to the values in the defined struct. See struct + module documentation for struct format string and its possible value types. + + :param int register_address: The register address to read the bit from + :param str struct_format: The struct format string for this register. + """ + + def __init__(self, register_address: int, struct_format: str) -> None: + self.format = struct_format + self.buffer = bytearray(1 + struct.calcsize(self.format)) + self.buffer[0] = register_address + + def __get__( + self, + obj: Optional[I2CDeviceDriver], + objtype: Optional[Type[I2CDeviceDriver]] = None, + ) -> Tuple: + with obj.i2c_device as i2c: + i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) + return struct.unpack_from(self.format, memoryview(self.buffer)[1:]) + + def __set__(self, obj: I2CDeviceDriver, value: Tuple) -> None: + struct.pack_into(self.format, self.buffer, 1, *value) + with obj.i2c_device as i2c: + i2c.write(self.buffer) + + +class UnaryStruct: + """ + Arbitrary single value structure register that is readable and writeable. + + Values map to the first value in the defined struct. See struct + module documentation for struct format string and its possible value types. + + :param int register_address: The register address to read the bit from + :param str struct_format: The struct format string for this register. + """ + + def __init__(self, register_address: int, struct_format: str) -> None: + self.format = struct_format + self.address = register_address + + def __get__( + self, + obj: Optional[I2CDeviceDriver], + objtype: Optional[Type[I2CDeviceDriver]] = None, + ) -> Any: + buf = bytearray(1 + struct.calcsize(self.format)) + buf[0] = self.address + with obj.i2c_device as i2c: + i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) + return struct.unpack_from(self.format, buf, 1)[0] + + def __set__(self, obj: I2CDeviceDriver, value: Any) -> None: + buf = bytearray(1 + struct.calcsize(self.format)) + buf[0] = self.address + struct.pack_into(self.format, buf, 1, value) + with obj.i2c_device as i2c: + i2c.write(buf) + + +class ROUnaryStruct(UnaryStruct): + """ + Arbitrary single value structure register that is read-only. + + Values map to the first value in the defined struct. See struct + module documentation for struct format string and its possible value types. + + :param int register_address: The register address to read the bit from + :param type struct_format: The struct format string for this register. + """ + + def __set__(self, obj: I2CDeviceDriver, value: Any) -> NoReturn: + raise AttributeError() diff --git a/src/lib/adafruit_register/i2c_struct_array.py b/src/lib/adafruit_register/i2c_struct_array.py new file mode 100644 index 0000000..65470c6 --- /dev/null +++ b/src/lib/adafruit_register/i2c_struct_array.py @@ -0,0 +1,114 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT +# pylint: disable=too-few-public-methods + +""" +`adafruit_register.i2c_struct_array` +==================================================== + +Array of structured registers based on `struct` + +* Author(s): Scott Shawcroft +""" + +__version__ = "1.10.1" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" + +import struct + +try: + from typing import Tuple, Optional, Type + from circuitpython_typing.device_drivers import I2CDeviceDriver +except ImportError: + pass + + +class _BoundStructArray: + """ + Array object that `StructArray` constructs on demand. + + :param object obj: The device object to bind to. It must have a `i2c_device` attribute + :param int register_address: The register address to read the bit from + :param str struct_format: The struct format string for each register element + :param int count: Number of elements in the array + """ + + def __init__( + self, + obj: I2CDeviceDriver, + register_address: int, + struct_format: str, + count: int, + ) -> None: + self.format = struct_format + self.first_register = register_address + self.obj = obj + self.count = count + + def _get_buffer(self, index: int) -> bytearray: + """Shared bounds checking and buffer creation.""" + if not 0 <= index < self.count: + raise IndexError() + size = struct.calcsize(self.format) + # We create the buffer every time instead of keeping the buffer (which is 32 bytes at least) + # around forever. + buf = bytearray(size + 1) + buf[0] = self.first_register + size * index + return buf + + def __getitem__(self, index: int) -> Tuple: + buf = self._get_buffer(index) + with self.obj.i2c_device as i2c: + i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) + return struct.unpack_from(self.format, buf, 1) # offset=1 + + def __setitem__(self, index: int, value: Tuple) -> None: + buf = self._get_buffer(index) + struct.pack_into(self.format, buf, 1, *value) + with self.obj.i2c_device as i2c: + i2c.write(buf) + + def __len__(self) -> int: + return self.count + + +class StructArray: + """ + Repeated array of structured registers that are readable and writeable. + + Based on the index, values are offset by the size of the structure. + + Values are tuples that map to the values in the defined struct. See struct + module documentation for struct format string and its possible value types. + + .. note:: This assumes the device addresses correspond to 8-bit bytes. This is not suitable for + devices with registers of other widths such as 16-bit. + + :param int register_address: The register address to begin reading the array from + :param str struct_format: The struct format string for this register. + :param int count: Number of elements in the array + """ + + def __init__(self, register_address: int, struct_format: str, count: int) -> None: + self.format = struct_format + self.address = register_address + self.count = count + self.array_id = "_structarray{}".format(register_address) + + def __get__( + self, + obj: Optional[I2CDeviceDriver], + objtype: Optional[Type[I2CDeviceDriver]] = None, + ) -> _BoundStructArray: + # We actually can't handle the indexing ourself due to data descriptor limits. So, we return + # an object that can instead. This object is bound to the object passed in here by its + # initializer and then cached on the object itself. That way its lifetime is tied to the + # lifetime of the object itself. + if not hasattr(obj, self.array_id): + setattr( + obj, + self.array_id, + _BoundStructArray(obj, self.address, self.format, self.count), + ) + return getattr(obj, self.array_id) diff --git a/src/lib/adafruit_requests.mpy b/src/lib/adafruit_requests.mpy new file mode 100644 index 0000000000000000000000000000000000000000..c2a1cc28e66ec0892ff2b4d91388b3decc411988 GIT binary patch literal 6944 zcmZu#Yj6`+mcA`pLKr_8cU?(VnR&j zB6t(hCnE_F?V8L)1aT%JoSR%si>NLK-z=&f8b!LJ$wj2+V?qv*qiMV)5=VMPs~)ss z$vNbhND1e}TsoDCpGZX);-Vs@)Kz8NIx%tFpGxM$i#cWP6=E3yE}LG=&4c5rovy56 z2ARX-kqhHuHl0dlMPwdb$c@g9i@8h;JR-N&8z;nU7GHohLdYu0(xpbYatMPdt z8BsWoq{~<<5{)HuDOB*2Vp2%KWDYfkg@`aOMiCjF2f3KZA_g=CF?L>zhT~##E;oq-hj%AIhT-1or{U6wv$0z_tFju7WW#DOJJtD?uc?{Wp&YXS5xiS*0^c2v)xe=$>a0@d=;gZTLL!|- zHQ>MP(&ZzUE+hSXDhqK7;4ih16*DfVYrr%W!sDaLxUsswp-{73XLa|Wy7^o#-G}P& zPqq(f`VfV~PyrmIR;d>p@Wxy$A*L1}4bSF;jHd*Z6AE@cHi8OGx~{Hn)RDaF5R(#Nu*CJK97w~Od?$xZ0p8Clc+8o$qDnQZtUo!KY;3^ zVqDAt-uZ#!1Cs;Dco^^|q!Q_u01)BxP<)ui!hm=HLplW`8Z%xvp|A=fCk6&a!UHFH zG}P@nK&CUPw3x{OvfB$Latl3r1$sKDRHVmuM~&6;kqA*bB56tSqJSw!L`FOUgfGA_ zS0zAHa7m}BgqTQW7SG4T3#ibYlKj$?)Y2Liak+*Uk};)h!?YUPFlIhfhw+w0(*8u@ z2(4gm9)K1H1`DbB1DQ-JgY*iRBg~HgQ(u8cVW2)C0xd#(A$aphw~&bgo8^Eb5~wW_ zk3&Ic#Apm^A7Hja`?8wv?(Vr*Zhm2=3*y;bZ2;Xzt6jyf67HBvxZ~9x1ACCR;RkGt zbOeL6dqT;`Ok8AgDF*W<6PTPFgOOUHT}Y>aQquOF6fcMwX1ub@C=3^6a`Uk)lS+zR z4B&N3nq^{%1o#bP5?=%h#3A^<4{586?W&Ni)&QwUW`Sm1TP&?5EA;ZrQe0^$$=ucw z1E*q2>SS<;86lDc&SGXnCL#!ceKE>JvJA!(lgcodnNW+{62r*;FRs!ht%(;1|FV%caW}q-{FH zVCX)xVyy1Qjl06H+O~k&E1ASXRta$|$wc9apjeFnq>iWx4D_=cw@$N;c$#FQ|1Di;6()Ut!Pn7&s|i%G~IoH7$jLPubL5kvrCA0U;Cq|@=3%3R$_ z;I;dnUApMHaN&YWxh_h0U z{Ggzaw!V@KmqIMs`e&mC7_Ux`s}Bk-9ZGKH2_r9;5>j!vjgAN)ttpvOA*{r#!rxl+ zxR@59c%qED!pufu@dYSJ^vkHjLrnMQ_gM#QcH7`_zO!@hz{P21*%W8~oFp~0)W9NI zYRm_B*U&Zat)-txa@OD@l{B2HF5RCXynXoi)4ZRugS%rD&?P znQoq%bR)9GqoG?S-2`biNCr<0ZJu;%$epmbbJATy?t;Otpio=r)YrGX`%?55+YOSD zmP}*|Em3Yn?|#!sOzbDCEp2E9aTvyr8h$st@dZH`LEE z`KG0v(Ix%-(w=40l5MGBseZ}4q+ixA?^teJGFCS2#G7ai`{^d={otb~kNvHoz3y7N zBN%jQCiZLT7u`DA;@(F0x%Kn`_jbC+T}S&3QnP!9hoE^psE0ut9yGvU$lXZSN@I9$ z6MZZwkjr}iL451oQfofOSvis6+Bx3Ab~rqYld*9SioGN&o4KHDwss=fY|G1LyHVP6 z)kDZ;2duV_$!4cab+8&Qu~GIrO0jjizzqEXswl$eT+hrm0#@T}Gg zqi&h%@uDfP)LW9x?w}CZUbkOr$rofn>iJDA?E&xhNfwFCzavuzuqLdR38NPW9dh$4 zIE#9rSn_IwchZ@I9k9&hS2&JoAFvN`o!kuL?CP}ey;jaHn;ds#lYOH^9W3U{-kN)> z`Oo>;QgF3P6S}`@l&nP$@$HA@_3wUHS}(&|DR`|*bLtvgcugpSxa1)=`DwVkI==d? zhxn*;J*dt=^ZH%5fkUReM#%=cg{7S3vEXFKh-o>4XG&2|Ae)B>Zu;Lp5JHfAbpAW942K*kZ@FT16{UguA_nu6>SX}jL?yrG?00dK+ zV5K4Xw5fgVn^(;3Yr@;+nYKQu<#7I+#quYFajm<^Rn4 zPM^M4zV91e19PP{ujU&t{zreW^z_UtPyF1}RI&VDKb8iCD?K~u0|3SE7^}}Z8o;sf z1tBADeiS}{^CK$GeORPo6Urqhywh{Evj_J5-!#KGShm*|aJAdoZQZWX3hM6^{eIv7 z=AkG6aS*?45WgPH-HkH!k{4~1_gl2>>EKTD5LnsYkF5-L zJ#WVTteHlPM#>L3f;h;gHrd2^G!;W{rQ4ZWFx1~~b#o2?c83)o&;O+Akkcr2I5p3j zYf`}73rKH~aTI&2wwtJ7+0^SL06Tr3IJfD|B=8!}}ndkNqbs9j%lwfKaefPg^ZLb4Q^@eucY$6yKO_t)m8e2rC3 zdnej+a@Svq{mVO+Y%ylJe%Z84N0+uOZHMkK@n?%sM%?;S=@7echfvyp+9OUHB-*V3 zk{`tb;9YtO4{Bi$!UL#i`Yg1IX=oP_XcsfkE(B;7(O@t*W;r|_bZ(p2zk^P=>*+V# z4YY)yS4bLn6D`%cNm|mm4YZ_pLkF!33Z_EOZc@f5PXvXP^k*c064bg^vNns~#qsQj&E;@%E+^O76L1dMM^0EfMr3LfFrMG&K=+E& zm=}ddeogdf?ti$R5B+Xk3VpOn1z7_%j%yd#=NiV_3A|>c#;0@qYG!Xqt@0C%?mrpi7z^tK1~`Rt|gshy}2pw4)a@Kzv3Pw^{%-4W()ODV`#Lekx%xdS}VUIOC) zAq;8%KuKX!7OOUc7e%Aw1_JitwhIkLe}V}~`&a(aVmW4?9AE=Zn|)xf4#fukxG%EGew_Zsf( zUg^NfZ3}BTGt6i9TkQkR{9WI_=L3N5*NP>z4Q>n{0ps4U`KfzDO{=CFV~y!2n>^ot zKd*@f?>TB;8{Px*wy*iny`deT2=cIpSe;d6#A{`^C{zya(MoR_-qI0E<)?QcC|LBw zzx*aV5CVR3T57>w{a&WdLQoimN#Tuf#r!HA!J-#d{$TAGE`>*!4TveUcbu?cnVK$^ z;g&$@)Qmc6gGSK*YU>Q%w14Fbi&Fl751aNt5#K9O`&PYB%Sy!qzn?9x;=UWgHrSx> z7IVGUBH*nDr59J8S}l(Dj$sGB3+^Ov7`$5`q-Tq43k+08FoA+2XSWX&JJz#~kgB*qq&I z?{GR@qqgoCdtzLsrq%!k!3Rn~om~%p<@Xy!@p|Q#H9s)IKM>ABg?+frXTiC31(^K& zmd4HpH8?W<2#RpsOFZ4(*%DG}PO1aum|8q~nH!oZEzJcNxTV;uRVA7U#M$=6HXnkn)eUI6-mg*dwhR&MY? zwrxv`-FpMp*8TqL{3%wCmm$SMkp)V(-@;`!wH6*R*bI1)Gpun2xc-#SwUw&dIFU2O zb#TEKUWZ<+f)RKKubg-vizCJN@uWW}5K`c1y;Lu~e6_=c)f-;_bEa>J6`cYPeM;m`R&yNY9yU z!4Xl6MN!I87Z zot#1TXl%{b`G1heMXnh?xBf=56gpwpb^6uM26dJzO`rLEuT1g3kg%9s4B2RorhZ0V z@)G}`qh)izs@Sz58zIThGl~U`d1tRyoahv)%nN%L*k9=pInShl;@ zRg;&!guks~<2FZkXkkCs3_V+>5=H1$YwOp%+icCs64wlj`ffh{Sf>d8pbB?Xh5rw4Y>r9* literal 0 HcmV?d00001 diff --git a/src/lib/adafruit_ticks.mpy b/src/lib/adafruit_ticks.mpy new file mode 100644 index 0000000000000000000000000000000000000000..6167b2f161ca616c28220d7ab71cab204e04c254 GIT binary patch literal 694 zcmYLEQE$>v7`-j)CF)J23Yq_nhy1=iKkxTLpJgv{hXpX6Wuyfr3 zSaW&znA^#b1E5#c+BS&U{jSkFF>R{{SjTJ|AknrumSc5HUF+CDSTZ2^rdquYVm_>! zjm84E4BG}s(@u<@ZCV{o15nd?M%NM|bxQEkb z$02LiT4{?6+-O-J!ME)n^e**2jbr8QQBst$r59fXvHbLuzgIpJM!^w_A)TR;ffS8s z6|SILTL4A(qZl%zx|(7TqaGYp`86J9=*!|ytl~;Ch=M%N2O(2Y0#zgwOEN%AVXz;^ zkR`5Qibd-xzpemPrVw+G_UwEt6SyqCQZWp9Jt4HA$~2LwcjU?e#K|itEHfxl(O2CB z%Q5&djtYVTM0Ylueg37p`FZg*DUxXM`zZfssl0(iF^PhN4WgYke=F2N{Bl=>Ts(n4 zr5|L)8O9h)s)y53NOb3TS}43#i_gZBD{(mIe|6#Z7Nt*IM-cPj6_KPSzZW_Ckl= thresh: + self.callback(cmd, addr, ext, *self.args) + else: + self._errf(cmd) + + def error_function(self, func): + self._errf = func + + def close(self): + self._pin.irq(handler = None) + self.tim.deinit() diff --git a/src/lib/ir_rx/acquire.py b/src/lib/ir_rx/acquire.py new file mode 100644 index 0000000..af3ab38 --- /dev/null +++ b/src/lib/ir_rx/acquire.py @@ -0,0 +1,108 @@ +# acquire.py Acquire a pulse train from an IR remote +# Supports NEC protocol. +# For a remote using NEC see https://www.adafruit.com/products/389 + +# Author: Peter Hinch +# Copyright Peter Hinch 2020 Released under the MIT license + +from machine import Pin, freq +from sys import platform + +from utime import sleep_ms, ticks_us, ticks_diff +from ir_rx import IR_RX + + +class IR_GET(IR_RX): + def __init__(self, pin, nedges=100, twait=100, display=True): + self.display = display + super().__init__(pin, nedges, twait, lambda *_ : None) + self.data = None + + def decode(self, _): + def near(v, target): + return target * 0.8 < v < target * 1.2 + lb = self.edge - 1 # Possible length of burst + if lb < 3: + return # Noise + burst = [] + for x in range(lb): + dt = ticks_diff(self._times[x + 1], self._times[x]) + if x > 0 and dt > 10000: # Reached gap between repeats + break + burst.append(dt) + lb = len(burst) # Actual length + # Duration of pulse train 24892 for RC-5 22205 for RC-6 + duration = ticks_diff(self._times[lb - 1], self._times[0]) + + if self.display: + for x, e in enumerate(burst): + print('{:03d} {:5d}'.format(x, e)) + print() + # Attempt to determine protocol + ok = False # Protocol not yet found + if near(burst[0], 9000) and lb == 67: + print('NEC') + ok = True + + if not ok and near(burst[0], 2400) and near(burst[1], 600): # Maybe Sony + try: + nbits = {25:12, 31:15, 41:20}[lb] + except KeyError: + pass + else: + ok = True + print('Sony {}bit'.format(nbits)) + + if not ok and near(burst[0], 889): # Maybe RC-5 + if near(duration, 24892) and near(max(burst), 1778): + print('Philps RC-5') + ok = True + + if not ok and near(burst[0], 2666) and near(burst[1], 889): # RC-6? + if near(duration, 22205) and near(burst[1], 889) and near(burst[2], 444): + print('Philips RC-6 mode 0') + ok = True + + if not ok and near(burst[0], 2000) and near(burst[1], 1000): + if near(duration, 19000): + print('Microsoft MCE edition protocol.') + # Constant duration, variable burst length, presumably bi-phase + print('Protocol start {} {} Burst length {} duration {}'.format(burst[0], burst[1], lb, duration)) + ok = True + + if not ok and near(burst[0], 4500) and near(burst[1], 4500) and lb == 67: # Samsung + print('Samsung') + ok = True + + if not ok and near(burst[0], 3500) and near(burst[1], 1680): # Panasonic? + print('Unsupported protocol. Panasonic?') + ok = True + + if not ok: + print('Unknown protocol start {} {} Burst length {} duration {}'.format(burst[0], burst[1], lb, duration)) + + print() + self.data = burst + # Set up for new data burst. Run null callback + self.do_callback(0, 0, 0) + + def acquire(self): + while self.data is None: + sleep_ms(5) + self.close() + return self.data + +def test(): + # Define pin according to platform + if platform == 'pyboard': + pin = Pin('X3', Pin.IN) + elif platform == 'esp8266': + freq(160000000) + pin = Pin(13, Pin.IN) + elif platform == 'esp32' or platform == 'esp32_LoBo': + pin = Pin(23, Pin.IN) + elif platform == 'rp2': + pin = Pin(16, Pin.IN) + irg = IR_GET(pin) + print('Waiting for IR data...') + return irg.acquire() diff --git a/src/lib/ir_rx/mce.py b/src/lib/ir_rx/mce.py new file mode 100644 index 0000000..c3e3849 --- /dev/null +++ b/src/lib/ir_rx/mce.py @@ -0,0 +1,68 @@ +# mce.py Decoder for IR remote control using synchronous code +# Supports Microsoft MCE edition remote protocol. + +# Author: Peter Hinch +# Copyright Peter Hinch 2020 Released under the MIT license + +# WARNING: This is experimental and subject to change. + +from utime import ticks_us, ticks_diff +from ir_rx import IR_RX + +class MCE(IR_RX): + init_cs = 4 # http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE says 3 + def __init__(self, pin, callback, *args): + # Block lasts ~19ms and has <= 34 edges + super().__init__(pin, 34, 25, callback, *args) + + def decode(self, _): + def check(v): + if self.init_cs == -1: + return True + csum = v >> 12 + cs = self.init_cs + for _ in range(12): + if v & 1: + cs += 1 + v >>= 1 + return cs == csum + + try: + t0 = ticks_diff(self._times[1], self._times[0]) # 2000μs mark + t1 = ticks_diff(self._times[2], self._times[1]) # 1000μs space + if not ((1800 < t0 < 2200) and (800 < t1 < 1200)): + raise RuntimeError(self.BADSTART) + nedges = self.edge # No. of edges detected + if not 14 <= nedges <= 34: + raise RuntimeError(self.OVERRUN if nedges > 28 else self.BADSTART) + # Manchester decode + mask = 1 + bit = 1 + v = 0 + x = 2 + for _ in range(16): + # -1 convert count to index, -1 because we look ahead + if x > nedges - 2: + raise RuntimeError(self.BADBLOCK) + # width is 500/1000 nominal + width = ticks_diff(self._times[x + 1], self._times[x]) + if not 250 < width < 1350: + self.verbose and print('Bad block 3 Width', width, 'x', x) + raise RuntimeError(self.BADBLOCK) + short = int(width < 750) + bit ^= short ^ 1 + v |= mask if bit else 0 + mask <<= 1 + x += 1 + short + + self.verbose and print(bin(v)) + if not check(v): + raise RuntimeError(self.BADDATA) + val = (v >> 6) & 0x3f + addr = v & 0xf # Constant for all buttons on my remote + ctrl = (v >> 4) & 3 + + except RuntimeError as e: + val, addr, ctrl = e.args[0], 0, 0 + # Set up for new data burst and run user callback/error function + self.do_callback(val, addr, ctrl) diff --git a/src/lib/ir_rx/nec.py b/src/lib/ir_rx/nec.py new file mode 100644 index 0000000..011b13d --- /dev/null +++ b/src/lib/ir_rx/nec.py @@ -0,0 +1,69 @@ +# nec.py Decoder for IR remote control using synchronous code +# Supports NEC and Samsung protocols. +# With thanks to J.E. Tannenbaum for information re Samsung protocol + +# For a remote using NEC see https://www.adafruit.com/products/389 + +# Author: Peter Hinch +# Copyright Peter Hinch 2020-2022 Released under the MIT license + +from utime import ticks_us, ticks_diff +from ir_rx import IR_RX + +class NEC_ABC(IR_RX): + def __init__(self, pin, extended, samsung, callback, *args): + # Block lasts <= 80ms (extended mode) and has 68 edges + super().__init__(pin, 68, 80, callback, *args) + self._extended = extended + self._addr = 0 + self._leader = 2500 if samsung else 4000 # 4.5ms for Samsung else 9ms + + def decode(self, _): + try: + if self.edge > 68: + raise RuntimeError(self.OVERRUN) + width = ticks_diff(self._times[1], self._times[0]) + if width < self._leader: # 9ms leading mark for all valid data + raise RuntimeError(self.BADSTART) + width = ticks_diff(self._times[2], self._times[1]) + if width > 3000: # 4.5ms space for normal data + if self.edge < 68: # Haven't received the correct number of edges + raise RuntimeError(self.BADBLOCK) + # Time spaces only (marks are always 562.5µs) + # Space is 1.6875ms (1) or 562.5µs (0) + # Skip last bit which is always 1 + val = 0 + for edge in range(3, 68 - 2, 2): + val >>= 1 + if ticks_diff(self._times[edge + 1], self._times[edge]) > 1120: + val |= 0x80000000 + elif width > 1700: # 2.5ms space for a repeat code. Should have exactly 4 edges. + raise RuntimeError(self.REPEAT if self.edge == 4 else self.BADREP) # Treat REPEAT as error. + else: + raise RuntimeError(self.BADSTART) + addr = val & 0xff # 8 bit addr + cmd = (val >> 16) & 0xff + if cmd != (val >> 24) ^ 0xff: + raise RuntimeError(self.BADDATA) + if addr != ((val >> 8) ^ 0xff) & 0xff: # 8 bit addr doesn't match check + if not self._extended: + raise RuntimeError(self.BADADDR) + addr |= val & 0xff00 # pass assumed 16 bit address to callback + self._addr = addr + except RuntimeError as e: + cmd = e.args[0] + addr = self._addr if cmd == self.REPEAT else 0 # REPEAT uses last address + # Set up for new data burst and run user callback + self.do_callback(cmd, addr, 0, self.REPEAT) + +class NEC_8(NEC_ABC): + def __init__(self, pin, callback, *args): + super().__init__(pin, False, False, callback, *args) + +class NEC_16(NEC_ABC): + def __init__(self, pin, callback, *args): + super().__init__(pin, True, False, callback, *args) + +class SAMSUNG(NEC_ABC): + def __init__(self, pin, callback, *args): + super().__init__(pin, True, True, callback, *args) diff --git a/src/lib/ir_rx/philips.py b/src/lib/ir_rx/philips.py new file mode 100644 index 0000000..cd7feaa --- /dev/null +++ b/src/lib/ir_rx/philips.py @@ -0,0 +1,123 @@ +# philips.py Decoder for IR remote control using synchronous code +# Supports Philips RC-5 RC-6 mode 0 protocols. + +# Author: Peter Hinch +# Copyright Peter Hinch 2020 Released under the MIT license + +from utime import ticks_us, ticks_diff +from ir_rx import IR_RX + +class RC5_IR(IR_RX): + def __init__(self, pin, callback, *args): + # Block lasts <= 30ms and has <= 28 edges + super().__init__(pin, 28, 30, callback, *args) + + def decode(self, _): + try: + nedges = self.edge # No. of edges detected + if not 14 <= nedges <= 28: + raise RuntimeError(self.OVERRUN if nedges > 28 else self.BADSTART) + # Regenerate bitstream + bits = 1 + bit = 1 + v = 1 # 14 bit bitstream, MSB always 1 + x = 0 + while bits < 14: + # -1 convert count to index, -1 because we look ahead + if x > nedges - 2: + print('Bad block 1 edges', nedges, 'x', x) + raise RuntimeError(self.BADBLOCK) + # width is 889/1778 nominal + width = ticks_diff(self._times[x + 1], self._times[x]) + if not 500 < width < 2100: + self.verbose and print('Bad block 3 Width', width, 'x', x) + raise RuntimeError(self.BADBLOCK) + short = width < 1334 + if not short: + bit ^= 1 + v <<= 1 + v |= bit + bits += 1 + x += 1 + int(short) + self.verbose and print(bin(v)) + # Split into fields (val, addr, ctrl) + val = (v & 0x3f) | (0 if ((v >> 12) & 1) else 0x40) # Correct the polarity of S2 + addr = (v >> 6) & 0x1f + ctrl = (v >> 11) & 1 + + except RuntimeError as e: + val, addr, ctrl = e.args[0], 0, 0 + # Set up for new data burst and run user callback + self.do_callback(val, addr, ctrl) + + +class RC6_M0(IR_RX): + # Even on Pyboard D the 444μs nominal pulses can be recorded as up to 705μs + # Scope shows 360-520 μs (-84μs +76μs relative to nominal) + # Header nominal 2666, 889, 444, 889, 444, 444, 444, 444 carrier ON at end + hdr = ((1800, 4000), (593, 1333), (222, 750), (593, 1333), (222, 750), (222, 750), (222, 750), (222, 750)) + def __init__(self, pin, callback, *args): + # Block lasts 23ms nominal and has <=44 edges + super().__init__(pin, 44, 30, callback, *args) + + def decode(self, _): + try: + nedges = self.edge # No. of edges detected + if not 22 <= nedges <= 44: + raise RuntimeError(self.OVERRUN if nedges > 28 else self.BADSTART) + for x, lims in enumerate(self.hdr): + width = ticks_diff(self._times[x + 1], self._times[x]) + if not (lims[0] < width < lims[1]): + self.verbose and print('Bad start', x, width, lims) + raise RuntimeError(self.BADSTART) + x += 1 + width = ticks_diff(self._times[x + 1], self._times[x]) + # 2nd bit of last 0 is 444μs (0) or 1333μs (1) + if not 222 < width < 1555: + self.verbose and print('Bad block 1 Width', width, 'x', x) + raise RuntimeError(self.BADBLOCK) + short = width < 889 + v = int(not short) + bit = v + bits = 1 # Bits decoded + x += 1 + int(short) + width = ticks_diff(self._times[x + 1], self._times[x]) + if not 222 < width < 1555: + self.verbose and print('Bad block 2 Width', width, 'x', x) + raise RuntimeError(self.BADBLOCK) + short = width < 1111 + if not short: + bit ^= 1 + x += 1 + int(short) # If it's short, we know width of next + v <<= 1 + v |= bit # MSB of result + bits += 1 + # Decode bitstream + while bits < 17: + # -1 convert count to index, -1 because we look ahead + if x > nedges - 2: + raise RuntimeError(self.BADBLOCK) + # width is 444/889 nominal + width = ticks_diff(self._times[x + 1], self._times[x]) + if not 222 < width < 1111: + self.verbose and print('Bad block 3 Width', width, 'x', x) + raise RuntimeError(self.BADBLOCK) + short = width < 666 + if not short: + bit ^= 1 + v <<= 1 + v |= bit + bits += 1 + x += 1 + int(short) + + if self.verbose: + ss = '20-bit format {:020b} x={} nedges={} bits={}' + print(ss.format(v, x, nedges, bits)) + + val = v & 0xff + addr = (v >> 8) & 0xff + ctrl = (v >> 16) & 1 + except RuntimeError as e: + val, addr, ctrl = e.args[0], 0, 0 + # Set up for new data burst and run user callback + self.do_callback(val, addr, ctrl) diff --git a/src/lib/ir_rx/print_error.py b/src/lib/ir_rx/print_error.py new file mode 100644 index 0000000..31ce51e --- /dev/null +++ b/src/lib/ir_rx/print_error.py @@ -0,0 +1,19 @@ +# print_error.py Error print for IR receiver + +# Author: Peter Hinch +# Copyright Peter Hinch 2020 Released under the MIT license + +from ir_rx import IR_RX + +_errors = {IR_RX.BADSTART : 'Invalid start pulse', + IR_RX.BADBLOCK : 'Error: bad block', + IR_RX.BADREP : 'Error: repeat', + IR_RX.OVERRUN : 'Error: overrun', + IR_RX.BADDATA : 'Error: invalid data', + IR_RX.BADADDR : 'Error: invalid address'} + +def print_error(data): + if data in _errors: + print(_errors[data]) + else: + print('Unknown error code:', data) diff --git a/src/lib/ir_rx/sony.py b/src/lib/ir_rx/sony.py new file mode 100644 index 0000000..1050356 --- /dev/null +++ b/src/lib/ir_rx/sony.py @@ -0,0 +1,70 @@ +# sony.py Decoder for IR remote control using synchronous code +# Sony SIRC protocol. + +# Author: Peter Hinch +# Copyright Peter Hinch 2020 Released under the MIT license + +from utime import ticks_us, ticks_diff +from ir_rx import IR_RX + +class SONY_ABC(IR_RX): # Abstract base class + def __init__(self, pin, bits, callback, *args): + # 20 bit block has 42 edges and lasts <= 39ms nominal. Add 4ms to time + # for tolerances except in 20 bit case where timing is tight with a + # repeat period of 45ms. + t = int(3 + bits * 1.8) + (1 if bits == 20 else 4) + super().__init__(pin, 2 + bits * 2, t, callback, *args) + self._addr = 0 + self._bits = 20 + + def decode(self, _): + try: + nedges = self.edge # No. of edges detected + self.verbose and print('nedges', nedges) + if nedges > 42: + raise RuntimeError(self.OVERRUN) + bits = (nedges - 2) // 2 + if nedges not in (26, 32, 42) or bits > self._bits: + raise RuntimeError(self.BADBLOCK) + self.verbose and print('SIRC {}bit'.format(bits)) + width = ticks_diff(self._times[1], self._times[0]) + if not 1800 < width < 3000: # 2.4ms leading mark for all valid data + raise RuntimeError(self.BADSTART) + width = ticks_diff(self._times[2], self._times[1]) + if not 350 < width < 1000: # 600μs space + raise RuntimeError(self.BADSTART) + + val = 0 # Data received, LSB 1st + x = 2 + bit = 1 + while x <= nedges - 2: + if ticks_diff(self._times[x + 1], self._times[x]) > 900: + val |= bit + bit <<= 1 + x += 2 + cmd = val & 0x7f # 7 bit command + val >>= 7 + if nedges < 42: + addr = val & 0xff # 5 or 8 bit addr + val = 0 + else: + addr = val & 0x1f # 5 bit addr + val >>= 5 # 8 bit extended + except RuntimeError as e: + cmd = e.args[0] + addr = 0 + val = 0 + self.do_callback(cmd, addr, val) + +class SONY_12(SONY_ABC): + def __init__(self, pin, callback, *args): + super().__init__(pin, 12, callback, *args) + +class SONY_15(SONY_ABC): + def __init__(self, pin, callback, *args): + super().__init__(pin, 15, callback, *args) + +class SONY_20(SONY_ABC): + def __init__(self, pin, callback, *args): + super().__init__(pin, 20, callback, *args) + diff --git a/src/lib/ir_rx/test.py b/src/lib/ir_rx/test.py new file mode 100644 index 0000000..c4dbd7f --- /dev/null +++ b/src/lib/ir_rx/test.py @@ -0,0 +1,70 @@ +# test.py Test program for IR remote control decoder +# Supports Pyboard, ESP32 and ESP8266 + +# Author: Peter Hinch +# Copyright Peter Hinch 2020-2022 Released under the MIT license + +# Run this to characterise a remote. + +from sys import platform +import time +import gc +from machine import Pin, freq +from ir_rx.print_error import print_error # Optional print of error codes + +# Import all implemented classes +from ir_rx.nec import NEC_8, NEC_16, SAMSUNG +from ir_rx.sony import SONY_12, SONY_15, SONY_20 +from ir_rx.philips import RC5_IR, RC6_M0 +from ir_rx.mce import MCE + +# Define pin according to platform +if platform == "pyboard": + p = Pin("X3", Pin.IN) +elif platform == "esp8266": + freq(160000000) + p = Pin(13, Pin.IN) +elif platform == "esp32" or platform == "esp32_LoBo": + p = Pin(23, Pin.IN) +elif platform == "rp2": + p = Pin(16, Pin.IN) + +# User callback +def cb(data, addr, ctrl): + if data < 0: # NEC protocol sends repeat codes. + print("Repeat code.") + else: + print(f"Data 0x{data:02x} Addr 0x{addr:04x} Ctrl 0x{ctrl:02x}") + + +def test(proto=0): + classes = (NEC_8, NEC_16, SONY_12, SONY_15, SONY_20, RC5_IR, RC6_M0, MCE, SAMSUNG) + ir = classes[proto](p, cb) # Instantiate receiver + ir.error_function(print_error) # Show debug information + # ir.verbose = True + # A real application would do something here... + try: + while True: + print("running") + time.sleep(5) + gc.collect() + except KeyboardInterrupt: + ir.close() + + +# **** DISPLAY GREETING **** +s = """Test for IR receiver. Run: +from ir_rx.test import test +test() for NEC 8 bit protocol, +test(1) for NEC 16 bit, +test(2) for Sony SIRC 12 bit, +test(3) for Sony SIRC 15 bit, +test(4) for Sony SIRC 20 bit, +test(5) for Philips RC-5 protocol, +test(6) for RC6 mode 0. +test(7) for Microsoft Vista MCE. +test(8) for Samsung. + +Hit ctrl-c to stop, then ctrl-d to soft reset.""" + +print(s)