diff --git a/.gitignore b/.gitignore index 544ec4a..05787ec 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,8 @@ _build .idea .vscode *~ + +# virtualenv +Pipfile +Pipfile.lock +Makefile diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7467c1d..a4cf3de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,6 +25,7 @@ repos: types: [python] args: - --disable=consider-using-f-string + - --min-similarity-lines=14 exclude: "^(docs/|examples/|tests/|setup.py$)" - id: pylint name: pylint (example code) diff --git a/.pylintrc b/.pylintrc index b86962e..edfacb5 100644 --- a/.pylintrc +++ b/.pylintrc @@ -252,8 +252,7 @@ ignore-docstrings=yes ignore-imports=yes # Minimum lines number of a similarity. -min-similarity-lines=4 - +min-similarity-lines=14 [BASIC] diff --git a/adafruit_imageload/__init__.py b/adafruit_imageload/__init__.py index 23e7151..4724276 100644 --- a/adafruit_imageload/__init__.py +++ b/adafruit_imageload/__init__.py @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: Matt Land # # SPDX-License-Identifier: MIT @@ -8,22 +9,42 @@ Load pixel values (indices or colors) into a bitmap and colors into a palette. -* Author(s): Scott Shawcroft +* Author(s): Scott Shawcroft, Matt Land """ # pylint: disable=import-outside-toplevel +try: + from typing import ( + Tuple, + Iterator, + Optional, + List, + Iterable, + Union, + ) + from io import BufferedReader + from displayio import Palette, Bitmap + from .displayio_types import PaletteConstructor, BitmapConstructor +except ImportError: + pass + __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -def load(file_or_filename, *, bitmap=None, palette=None): +def load( + file_or_filename: Union[str, BufferedReader], + *, + bitmap: Optional[BitmapConstructor] = None, + palette: Optional[PaletteConstructor] = None +) -> Tuple[Bitmap, Optional[Palette]]: """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 pallete type. The constructor should take the number of colors and + 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: diff --git a/adafruit_imageload/bmp/__init__.py b/adafruit_imageload/bmp/__init__.py index a8cd86d..86372a8 100644 --- a/adafruit_imageload/bmp/__init__.py +++ b/adafruit_imageload/bmp/__init__.py @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: Matt Land # # SPDX-License-Identifier: MIT @@ -8,16 +9,29 @@ Load pixel values (indices or colors) into a bitmap and colors into a palette from a BMP file. -* Author(s): Scott Shawcroft +* Author(s): Scott Shawcroft, Matt Land """ # pylint: disable=import-outside-toplevel +try: + from typing import Tuple, Optional, Set, List + from io import BufferedReader + from displayio import Palette, Bitmap + from ..displayio_types import PaletteConstructor, BitmapConstructor +except ImportError: + pass + __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -def load(file, *, bitmap=None, palette=None): +def load( + file: BufferedReader, + *, + bitmap: BitmapConstructor = None, + palette: PaletteConstructor = None +) -> Tuple[Bitmap, Optional[Palette]]: """Loads a bmp image from the open ``file``. Returns tuple of bitmap object and palette object. diff --git a/adafruit_imageload/bmp/indexed.py b/adafruit_imageload/bmp/indexed.py index ba39060..079e19f 100755 --- a/adafruit_imageload/bmp/indexed.py +++ b/adafruit_imageload/bmp/indexed.py @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: Matt Land # # SPDX-License-Identifier: MIT @@ -8,33 +9,43 @@ Load pixel values (indices or colors) into a bitmap and colors into a palette from an indexed BMP. -* Author(s): Scott Shawcroft +* Author(s): Scott Shawcroft, Matt Land """ -__version__ = "0.0.0-auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" import sys +try: + from typing import Tuple, Optional + from io import BufferedReader + from displayio import Palette, Bitmap + from ..displayio_types import PaletteConstructor, BitmapConstructor +except ImportError: + pass + try: from bitmaptools import readinto as _bitmap_readinto except ImportError: - _bitmap_readinto = None # pylint: disable=invalid-name + _bitmap_readinto = None # pylint: disable=invalid-name # type: Callable + + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" def load( - file, - width, - height, - data_start, - colors, - color_depth, - compression, + file: BufferedReader, + width: int, + height: int, + data_start: int, + colors: int, + color_depth: int, + compression: int, *, - bitmap=None, - palette=None -): + bitmap: BitmapConstructor = None, + palette: PaletteConstructor = None, +) -> Tuple[Bitmap, Optional[Palette]]: """Loads indexed bitmap data into bitmap and palette objects. :param file file: The open bmp file @@ -46,7 +57,7 @@ def load( :param int compression: 0 - none, 1 - 8bit RLE, 2 - 4bit RLE""" # pylint: disable=too-many-arguments,too-many-locals,too-many-branches if palette: - palette = palette(colors) + palette = palette(colors) # type: Palette file.seek(data_start - colors * 4) for value in range(colors): @@ -67,7 +78,7 @@ def load( # convert unsigned int to signed int when height is negative height = negative_height_check(height) - bitmap = bitmap(width, abs(height), colors) + bitmap = bitmap(width, abs(height), colors) # type: Bitmap file.seek(data_start) line_size = width // (8 // color_depth) if width % (8 // color_depth) != 0: @@ -122,7 +133,13 @@ def load( return bitmap, palette -def decode_rle(bitmap, file, compression, y_range, width): +def decode_rle( + bitmap: Bitmap, + file: BufferedReader, + compression: int, + y_range: Tuple[int, int, int], + width: int, +) -> None: """Helper to decode RLE images""" # pylint: disable=too-many-locals,too-many-nested-blocks,too-many-branches diff --git a/adafruit_imageload/bmp/negative_height_check.py b/adafruit_imageload/bmp/negative_height_check.py index 99317a8..2f61d6f 100644 --- a/adafruit_imageload/bmp/negative_height_check.py +++ b/adafruit_imageload/bmp/negative_height_check.py @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: 2018 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: Matt Land # # SPDX-License-Identifier: MIT @@ -6,10 +7,12 @@ 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): +def negative_height_check(height: int) -> int: """Check the height return modified if negative.""" if height > 0x7FFFFFFF: return height - 4294967296 diff --git a/adafruit_imageload/displayio_types.py b/adafruit_imageload/displayio_types.py new file mode 100644 index 0000000..e2ab61b --- /dev/null +++ b/adafruit_imageload/displayio_types.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 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 Palette, Bitmap + + PaletteConstructor = Callable[[int], Palette] + BitmapConstructor = Callable[[int, int, int], Bitmap] +except ImportError: + pass + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" diff --git a/adafruit_imageload/gif.py b/adafruit_imageload/gif.py index 8f1dc04..3bf45d0 100644 --- a/adafruit_imageload/gif.py +++ b/adafruit_imageload/gif.py @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: 2019 Radomir Dopieralski for Adafruit Industries +# SPDX-FileCopyrightText: Matt Land # # SPDX-License-Identifier: MIT @@ -9,22 +10,35 @@ Load pixel values (indices or colors) into a bitmap and colors into a palette from a GIF file. -* Author(s): Radomir Dopieralski +* Author(s): Radomir Dopieralski, Matt Land """ import struct +try: + from typing import Tuple, Iterator, Optional, List + from io import BufferedReader + from displayio import Palette, Bitmap + from .displayio_types import PaletteConstructor, BitmapConstructor +except ImportError: + pass __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -def load(file, *, bitmap=None, palette=None): +def load( + file: BufferedReader, + *, + bitmap: BitmapConstructor, + palette: PaletteConstructor = None +) -> Tuple[Bitmap, Optional[Palette]]: """Loads a GIF image from the open ``file``. Returns tuple of bitmap object and palette object. + :param BufferedReader file: The *.gif file being loaded :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 @@ -32,7 +46,9 @@ def load(file, *, bitmap=None, palette=None): header = file.read(6) if header not in {b"GIF87a", b"GIF89a"}: raise ValueError("Not a GIF file") - width, height, flags, _, _ = struct.unpack(" None: + """Read a single frame and apply it to the bitmap.""" + ddx, ddy, width, _, flags = struct.unpack( # pylint: disable=no-member + " Iterator[int]: """Read a block from a file.""" while True: size = file.read(1)[0] @@ -95,21 +113,21 @@ class EndOfData(Exception): class LZWDict: """A dictionary of LZW codes.""" - def __init__(self, code_size): + 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 = [] - self.last = None + self.codes = [] # type: List[bytes] + self.last = b"" self.clear() - def clear(self): + 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): + def decode(self, code: int) -> bytes: """Decode a code.""" if code == self.clear_code: self.clear() @@ -133,7 +151,7 @@ def decode(self, code): return value -def lzw_decode(data, code_size): +def lzw_decode(data: Iterator[int], code_size: int) -> Iterator[bytes]: """Decode LZW-compressed data.""" dictionary = LZWDict(code_size) bit = 0 diff --git a/adafruit_imageload/pnm/__init__.py b/adafruit_imageload/pnm/__init__.py index d9631d9..5a79c65 100644 --- a/adafruit_imageload/pnm/__init__.py +++ b/adafruit_imageload/pnm/__init__.py @@ -16,13 +16,35 @@ """ # pylint: disable=import-outside-toplevel +try: + from typing import ( + Tuple, + Iterator, + Optional, + List, + Iterable, + Union, + Callable, + ) + from io import BufferedReader + from displayio import Palette, Bitmap + from ..displayio_types import PaletteConstructor, BitmapConstructor +except ImportError: + pass + __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -def load(file, header, *, bitmap=None, palette=None): +def load( + file: BufferedReader, + header: bytes, + *, + bitmap: BitmapConstructor = None, + palette: PaletteConstructor = None +) -> Tuple[Optional[Bitmap], Optional[Palette]]: """ - Scan for netpbm format info, skip over comments, and and delegate to a submodule + 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. @@ -31,7 +53,7 @@ def load(file, header, *, bitmap=None, palette=None): # pylint: disable=too-many-branches magic_number = header[:2] file.seek(2) - pnm_header = [] + pnm_header = [] # type: List[int] next_value = bytearray() while True: # We have all we need at length 3 for formats P2, P3, P5, P6 @@ -40,44 +62,64 @@ def load(file, header, *, bitmap=None, palette=None): from . import pgm return pgm.load( - file, magic_number, pnm_header, bitmap=bitmap, palette=palette + 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 + 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 + file, + pnm_header[0], + pnm_header[1], + bitmap=bitmap, + palette=palette, ) if len(pnm_header) == 2 and magic_number in [b"P1", b"P4"]: - bitmap = bitmap(pnm_header[0], pnm_header[1], 1) + bitmap = bitmap(pnm_header[0], pnm_header[1], 1) # type: Bitmap if palette: - palette = palette(1) + palette = palette(1) # type: Palette palette[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, palette=palette + file, + pnm_header[0], + pnm_header[1], + bitmap=bitmap, + palette=palette, ) from . import pbm_binary return pbm_binary.load( - file, pnm_header[0], pnm_header[1], bitmap=bitmap, palette=palette + file, + pnm_header[0], + pnm_header[1], + bitmap=bitmap, + palette=palette, ) next_byte = file.read(1) if next_byte == b"": - raise RuntimeError("Unsupported image format {}".format(magic_number)) + raise RuntimeError("Unsupported image format {!r}".format(magic_number)) 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 diff --git a/adafruit_imageload/pnm/pbm_ascii.py b/adafruit_imageload/pnm/pbm_ascii.py index c0206f9..d0eac59 100644 --- a/adafruit_imageload/pnm/pbm_ascii.py +++ b/adafruit_imageload/pnm/pbm_ascii.py @@ -16,15 +16,28 @@ """ +try: + from typing import Tuple, Optional + from io import BufferedReader + from displayio import Palette, Bitmap +except ImportError: + pass + __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -def load(file, width, height, bitmap=None, palette=None): +def load( + file: BufferedReader, + width: int, + height: int, + bitmap: Bitmap, + palette: Palette = None, +) -> Tuple[Bitmap, Optional[Palette]]: """ Load a P1 'PBM' ascii image into the displayio.Bitmap """ - next_byte = True + next_byte = b"1" # just to start the iterator for y in range(height): x = 0 while next_byte: diff --git a/adafruit_imageload/pnm/pbm_binary.py b/adafruit_imageload/pnm/pbm_binary.py index 0bd6ce1..6a801ce 100644 --- a/adafruit_imageload/pnm/pbm_binary.py +++ b/adafruit_imageload/pnm/pbm_binary.py @@ -15,14 +15,26 @@ * Author(s): Matt Land, Brooke Storm, Sam McGahan """ +try: + from typing import Tuple, Optional, Iterator + from io import BufferedReader + from displayio import Palette, Bitmap +except ImportError: + pass __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -def load(file, width, height, bitmap=None, palette=None): +def load( + file: BufferedReader, + width: int, + height: int, + bitmap: Bitmap, + palette: Palette = None, +) -> Tuple[Bitmap, Optional[Palette]]: """ - Load a P4 'PBM' binary image into the displayio.Bitmap + Load a P4 'PBM' binary image into the Bitmap """ x = 0 y = 0 @@ -41,7 +53,7 @@ def load(file, width, height, bitmap=None, palette=None): return bitmap, palette -def iterbits(b): +def iterbits(b: bytes) -> Iterator[int]: """ generator to iterate over the bits in a byte (character) """ @@ -50,7 +62,7 @@ def iterbits(b): yield (in_char >> i) & 1 -def reverse(b): +def reverse(b: int) -> int: """ reverse bit order so the iterbits works """ diff --git a/adafruit_imageload/pnm/pgm/__init__.py b/adafruit_imageload/pnm/pgm/__init__.py index e496327..08182b0 100644 --- a/adafruit_imageload/pnm/pgm/__init__.py +++ b/adafruit_imageload/pnm/pgm/__init__.py @@ -15,9 +15,23 @@ """ # pylint: disable=import-outside-toplevel - - -def load(file, magic_number, header, *, bitmap=None, palette=None): +try: + from typing import Tuple, Optional, Set, List + from io import BufferedReader + from displayio import Palette, Bitmap + from ...displayio_types import PaletteConstructor, BitmapConstructor +except ImportError: + pass + + +def load( + file: BufferedReader, + magic_number: bytes, + header: List[int], + *, + bitmap: BitmapConstructor = None, + palette: PaletteConstructor = None +) -> Tuple[Optional[Bitmap], Optional[Palette]]: """ Perform the load of Netpbm greyscale images (P2, P5) """ diff --git a/adafruit_imageload/pnm/pgm/ascii.py b/adafruit_imageload/pnm/pgm/ascii.py index 777f0c4..4d6db53 100644 --- a/adafruit_imageload/pnm/pgm/ascii.py +++ b/adafruit_imageload/pnm/pgm/ascii.py @@ -14,9 +14,22 @@ * Author(s): Matt Land, Brooke Storm, Sam McGahan """ +try: + from typing import Tuple, Set, Optional + from io import BufferedReader + from displayio import Palette, Bitmap + from ...displayio_types import PaletteConstructor, BitmapConstructor +except ImportError: + pass -def load(file, width, height, bitmap=None, palette=None): +def load( + file: BufferedReader, + width: int, + height: int, + bitmap: BitmapConstructor = None, + palette: PaletteConstructor = None, +) -> Tuple[Optional[Bitmap], Optional[Palette]]: """ Load a PGM ascii file (P2) """ @@ -34,9 +47,9 @@ def load(file, width, height, bitmap=None, palette=None): pixel = bytearray() pixel += byte if palette: - palette = build_palette(palette, _palette_colors) + palette = build_palette(palette, _palette_colors) # type: Palette if bitmap: - bitmap = bitmap(width, height, len(_palette_colors)) + bitmap = bitmap(width, height, len(_palette_colors)) # type: Bitmap _palette_colors = list(_palette_colors) file.seek(data_start) for y in range(height): @@ -52,7 +65,9 @@ def load(file, width, height, bitmap=None, palette=None): return bitmap, palette -def build_palette(palette_class, palette_colors): # pylint: disable=duplicate-code +def build_palette( + palette_class: PaletteConstructor, palette_colors: Set[int] +) -> Palette: # pylint: disable=duplicate-code """ construct the Palette, and populate it with the set of palette_colors """ diff --git a/adafruit_imageload/pnm/pgm/binary.py b/adafruit_imageload/pnm/pgm/binary.py index dcacabd..e1c8cbf 100644 --- a/adafruit_imageload/pnm/pgm/binary.py +++ b/adafruit_imageload/pnm/pgm/binary.py @@ -14,13 +14,26 @@ * Author(s): Matt Land, Brooke Storm, Sam McGahan """ +try: + from typing import Tuple, Optional, Set, List + from io import BufferedReader + from displayio import Palette, Bitmap + from ...displayio_types import PaletteConstructor, BitmapConstructor +except ImportError: + pass -def load(file, width, height, bitmap=None, palette=None): +def load( + file: BufferedReader, + width: int, + height: int, + bitmap: BitmapConstructor = None, + palette: PaletteConstructor = None, +) -> Tuple[Optional[Bitmap], Optional[Palette]]: """ Load a P5 format file (binary), handle PGM (greyscale) """ - palette_colors = set() + palette_colors = set() # type: Set[int] data_start = file.tell() for y in range(height): data_line = iter(bytes(file.read(width))) @@ -28,10 +41,10 @@ def load(file, width, height, bitmap=None, palette=None): palette_colors.add(pixel) if palette: - palette = build_palette(palette, palette_colors) + palette = build_palette(palette, palette_colors) # type: Palette if bitmap: - bitmap = bitmap(width, height, len(palette_colors)) - palette_colors = list(palette_colors) + bitmap = bitmap(width, height, len(palette_colors)) # type: Bitmap + palette_colors = list(palette_colors) # type: List[int] file.seek(data_start) for y in range(height): data_line = iter(bytes(file.read(width))) @@ -40,7 +53,9 @@ def load(file, width, height, bitmap=None, palette=None): return bitmap, palette -def build_palette(palette_class, palette_colors): +def build_palette( + palette_class: PaletteConstructor, palette_colors: Set[int] +) -> Palette: """ construct the Palette, and populate it with the set of palette_colors """ diff --git a/adafruit_imageload/pnm/ppm_ascii.py b/adafruit_imageload/pnm/ppm_ascii.py index 2c7929a..678067c 100644 --- a/adafruit_imageload/pnm/ppm_ascii.py +++ b/adafruit_imageload/pnm/ppm_ascii.py @@ -19,8 +19,28 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" +try: + from typing import ( + Tuple, + Iterator, + Optional, + List, + Set, + ) + from io import BufferedReader + from displayio import Palette, Bitmap + from ..displayio_types import PaletteConstructor, BitmapConstructor +except ImportError: + pass -def load(file, width, height, bitmap=None, palette=None): + +def load( + file: BufferedReader, + width: int, + height: int, + bitmap: BitmapConstructor = None, + palette: PaletteConstructor = None, +) -> Tuple[Optional[Bitmap], Optional[Palette]]: """ :param stream file: infile with the position set at start of data :param int width: @@ -30,19 +50,19 @@ def load(file, width, height, bitmap=None, palette=None): :param palette: displayio.Palette class :return tuple: """ - palette_colors = set() + palette_colors = set() # type: Set[bytes] data_start = file.tell() for triplet in read_three_colors(file): palette_colors.add(triplet) if palette: - palette = palette(len(palette_colors)) + palette = palette(len(palette_colors)) # type: Palette for counter, color in enumerate(palette_colors): palette[counter] = color if bitmap: file.seek(data_start) - bitmap = bitmap(width, height, len(palette_colors)) - palette_colors = list(palette_colors) + bitmap = bitmap(width, height, len(palette_colors)) # type: Bitmap + palette_colors = list(palette_colors) # type: List[bytes] for y in range(height): for x in range(width): for color in read_three_colors(file): @@ -51,13 +71,13 @@ def load(file, width, height, bitmap=None, palette=None): return bitmap, palette -def read_three_colors(file): +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 tuple[int]: """ - triplet = [] + triplet = [] # type: List[int] color = bytearray() while True: this_byte = file.read(1) diff --git a/adafruit_imageload/pnm/ppm_binary.py b/adafruit_imageload/pnm/ppm_binary.py index 8ecb5e8..900ddfc 100644 --- a/adafruit_imageload/pnm/ppm_binary.py +++ b/adafruit_imageload/pnm/ppm_binary.py @@ -15,17 +15,30 @@ * Author(s): Matt Land, Brooke Storm, Sam McGahan """ +try: + from typing import Tuple, Optional, List, Set + from io import BufferedReader + from displayio import Palette, Bitmap + from ..displayio_types import PaletteConstructor, BitmapConstructor +except ImportError: + pass __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -def load(file, width, height, bitmap=None, palette=None): +def load( + file: BufferedReader, + width: int, + height: int, + bitmap: BitmapConstructor = None, + palette: 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() + palette_colors = set() # type: Set[Tuple[int, int, int]] line_size = width * 3 for y in range(height): @@ -35,13 +48,13 @@ def load(file, width, height, bitmap=None, palette=None): palette_colors.add((red, next(data_line), next(data_line))) if palette: - palette = palette(len(palette_colors)) + palette = palette(len(palette_colors)) # type: Palette for counter, color in enumerate(palette_colors): palette[counter] = bytes(color) if bitmap: - bitmap = bitmap(width, height, len(palette_colors)) + bitmap = bitmap(width, height, len(palette_colors)) # type: Bitmap file.seek(data_start) - palette_colors = list(palette_colors) + palette_colors = list(palette_colors) # type: List[Tuple[int, int, int]] for y in range(height): x = 0 data_line = iter(bytes(file.read(line_size))) diff --git a/adafruit_imageload/tilegrid_inflator.py b/adafruit_imageload/tilegrid_inflator.py new file mode 100644 index 0000000..5137b42 --- /dev/null +++ b/adafruit_imageload/tilegrid_inflator.py @@ -0,0 +1,108 @@ +# SPDX-FileCopyrightText: 2022 Tim Cocks for Adafruit Industries +# SPDX-FileCopyrightText: 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 Tuple, Optional, List, Union + from displayio import Palette, Bitmap, OnDiskBitmap, TileGrid +except ImportError: + pass + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + + +def inflate_tilegrid( + bmp_path: str = None, + target_size: Tuple[int, int] = (3, 3), + tile_size: 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 Optional[tuple] target_size: desired size in tiles (target_width, target_height) + :param Optional[tuple] 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 + """ + + # pylint: disable=too-many-arguments, too-many-locals, too-many-branches + + 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/docs/api.rst b/docs/api.rst index 5ef6ad1..0cf10aa 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -12,3 +12,6 @@ .. automodule:: adafruit_imageload.bmp.indexed :members: + +.. automodule:: adafruit_imageload.tilegrid_inflator + :members: diff --git a/docs/developing.rst b/docs/developing.rst index 7b1d345..9b6a8fd 100644 --- a/docs/developing.rst +++ b/docs/developing.rst @@ -115,3 +115,18 @@ For example, the Bitmap coordinate ``[0,0]`` has the value (integer) ``5``. This corresponds to the the Palette object's, ``[5]`` which is ``b'\x00\x00\xff\x00'``. This is a byte string that represents a color. + +==================== +Mypy & type checking +==================== + +Mypy was tested with version 0.950 and the mypy.ini in project root. +Since checks are not currently not passing, it is not installed as a commit hook. + +Setup: In your virtual environment, run: + + pip3 install mypy==0.050 + +Developing: To manually run checks, run: + + mypy . diff --git a/docs/examples.rst b/docs/examples.rst index 039d649..6dc236d 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -15,3 +15,12 @@ Loads image that is fetched using adafruit_request .. literalinclude:: ../examples/imageload_from_web.py :caption: examples/imageload_from_web.py :linenos: + +Inflate TileGrid test +--------------------- + +Load 3x3 spritesheet and inflate it to a larger sized TileGrid + +.. literalinclude:: ../examples/imageload_tilegrid_inflator_simpletest.py + :caption: examples/imageload_tilegrid_inflator_simpletest.py + :linenos: diff --git a/examples/imageload_tilegrid_inflator_simpletest.py b/examples/imageload_tilegrid_inflator_simpletest.py new file mode 100644 index 0000000..5024b6b --- /dev/null +++ b/examples/imageload_tilegrid_inflator_simpletest.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2022 Tim Cocks for Adafruit Industries +# SPDX-License-Identifier: MIT + +import board +import displayio +import adafruit_imageload +from adafruit_imageload.tilegrid_inflator import inflate_tilegrid + +image, palette = adafruit_imageload.load("images/castle_spritesheet.bmp") +tile_grid = inflate_tilegrid(bmp_obj=image, bmp_palette=palette, target_size=(10, 8)) + +group = displayio.Group() +group.append(tile_grid) +board.DISPLAY.show(group) + +while True: + pass diff --git a/examples/images/castle_spritesheet.bmp b/examples/images/castle_spritesheet.bmp new file mode 100644 index 0000000..587bef7 Binary files /dev/null and b/examples/images/castle_spritesheet.bmp differ diff --git a/examples/images/castle_spritesheet.bmp.license b/examples/images/castle_spritesheet.bmp.license new file mode 100644 index 0000000..a784acf --- /dev/null +++ b/examples/images/castle_spritesheet.bmp.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..5000fb4 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2022 Matt Land +# +# SPDX-License-Identifier: Unlicense +[mypy] +python_version = 3.9 +disallow_untyped_defs = True +disable_error_code = no-redef +exclude = (examples|tests|setup.py|docs)