]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/commitdiff
Basic fontio wrapper added
authorMelissa LeBlanc-Williams <melissa@adafruit.com>
Fri, 22 May 2020 16:30:45 +0000 (09:30 -0700)
committerMelissa LeBlanc-Williams <melissa@adafruit.com>
Fri, 22 May 2020 16:30:45 +0000 (09:30 -0700)
displayio.py
fontio.py [new file with mode: 0644]
terminalio.py

index 200f2a90f952c0d11e7f94bd9b61434589007547..fb69401e7226dbc8fba88a73ff7ac53a1cfb6675 100644 (file)
@@ -30,14 +30,9 @@ __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
 _displays = []
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
 _displays = []
-_groups = []
 
 Rectangle = namedtuple("Rectangle", "x1 y1 x2 y2")
 
 
 Rectangle = namedtuple("Rectangle", "x1 y1 x2 y2")
 
-class _DisplayioSingleton:
-    def __init__(self):
-        pass
-
 
 def release_displays():
     """Releases any actively used displays so their busses and pins can be used again.
 
 def release_displays():
     """Releases any actively used displays so their busses and pins can be used again.
@@ -48,6 +43,7 @@ def release_displays():
         _disp._release()
     _displays.clear()
 
         _disp._release()
     _displays.clear()
 
+
 class Bitmap:
     """Stores values of a certain size in a 2D array"""
 
 class Bitmap:
     """Stores values of a certain size in a 2D array"""
 
@@ -86,7 +82,9 @@ class Bitmap:
         an x,y tuple or an int equal to `y * width + x`.
         """
         if isinstance(index, (tuple, list)):
         an x,y tuple or an int equal to `y * width + x`.
         """
         if isinstance(index, (tuple, list)):
-            index = index[1] * self._width + index[0]
+            index = (index[1] * self._width) + index[0]
+        if index >= len(self._data):
+            raise ValueError("Index {} is out of range".format(index))
         return self._data[index]
 
     def __setitem__(self, index, value):
         return self._data[index]
 
     def __setitem__(self, index, value):
@@ -350,7 +348,7 @@ class Display:
         time.sleep(1)
         # Eventually calculate dirty rectangles here
         self._subrectangles.append(Rectangle(0, 0, self._width, self._height))
         time.sleep(1)
         # Eventually calculate dirty rectangles here
         self._subrectangles.append(Rectangle(0, 0, self._width, self._height))
-        
+
         for area in self._subrectangles:
             self._refresh_display_area(area)
 
         for area in self._subrectangles:
             self._refresh_display_area(area)
 
@@ -367,16 +365,22 @@ class Display:
             | ((data[:, :, 1] & 0xFC) << 3)
             | (data[:, :, 2] >> 3)
         )
             | ((data[:, :, 1] & 0xFC) << 3)
             | (data[:, :, 2] >> 3)
         )
-        
-        pixels = list(numpy.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist())
-        
+
+        pixels = list(
+            numpy.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist()
+        )
+
         self._write(
             self._set_column_command,
         self._write(
             self._set_column_command,
-            self._encode_pos(rectangle.x1 + self._colstart, rectangle.x2 + self._colstart)
+            self._encode_pos(
+                rectangle.x1 + self._colstart, rectangle.x2 + self._colstart
+            ),
         )
         self._write(
             self._set_row_command,
         )
         self._write(
             self._set_row_command,
-            self._encode_pos(rectangle.y1 + self._rowstart, rectangle.y2 + self._rowstart)
+            self._encode_pos(
+                rectangle.y1 + self._rowstart, rectangle.y2 + self._rowstart
+            ),
         )
         self._write(self._write_ram_command, pixels)
 
         )
         self._write(self._write_ram_command, pixels)
 
@@ -395,7 +399,9 @@ class Display:
     def auto_refresh(self, value):
         self._auto_refresh = value
         if self._refresh_thread is None:
     def auto_refresh(self, value):
         self._auto_refresh = value
         if self._refresh_thread is None:
-            self._refresh_thread = threading.Thread(target=self._refresh_loop, daemon=True)
+            self._refresh_thread = threading.Thread(
+                target=self._refresh_loop, daemon=True
+            )
         if value and not self._refresh_thread.is_alive():
             # Start the thread
             self._refresh_thread.start()
         if value and not self._refresh_thread.is_alive():
             # Start the thread
             self._refresh_thread.start()
@@ -596,7 +602,6 @@ class Group:
         self._hidden = False
         self._layers = []
         self._supported_types = (TileGrid, Group)
         self._hidden = False
         self._layers = []
         self._supported_types = (TileGrid, Group)
-        print("Creating Group")
 
     def append(self, layer):
         """Append a layer to the group. It will be drawn
 
     def append(self, layer):
         """Append a layer to the group. It will be drawn
@@ -732,13 +737,14 @@ class OnDiskBitmap:
         """Height of the bitmap. (read only)"""
         return self._image.height
 
         """Height of the bitmap. (read only)"""
         return self._image.height
 
+
 class Palette:
     """Map a pixel palette_index to a full color. Colors are transformed to the display’s format internally to save memory."""
 
     def __init__(self, color_count):
         """Create a Palette object to store a set number of colors."""
         self._needs_refresh = False
 class Palette:
     """Map a pixel palette_index to a full color. Colors are transformed to the display’s format internally to save memory."""
 
     def __init__(self, color_count):
         """Create a Palette object to store a set number of colors."""
         self._needs_refresh = False
-        
+
         self._colors = []
         for _ in range(color_count):
             self._colors.append(self._make_color(0))
         self._colors = []
         for _ in range(color_count):
             self._colors.append(self._make_color(0))
@@ -779,10 +785,11 @@ class Palette:
         return self._colors[index]
 
     def make_transparent(self, palette_index):
         return self._colors[index]
 
     def make_transparent(self, palette_index):
-        self._colors[palette_index].transparent = True
+        self._colors[palette_index]["transparent"] = True
 
     def make_opaque(self, palette_index):
 
     def make_opaque(self, palette_index):
-        self._colors[palette_index].transparent = False
+        self._colors[palette_index]["transparent"] = False
+
 
 class ParallelBus:
     """Manage updating a display over 8-bit parallel bus in the background while Python code runs.
 
 class ParallelBus:
     """Manage updating a display over 8-bit parallel bus in the background while Python code runs.
@@ -853,15 +860,15 @@ class TileGrid:
         self._bitmap = bitmap
         bitmap_width = bitmap.width
         bitmap_height = bitmap.height
         self._bitmap = bitmap
         bitmap_width = bitmap.width
         bitmap_height = bitmap.height
-        
+
         if not isinstance(pixel_shader, (ColorConverter, Palette)):
             raise ValueError("Unsupported Pixel Shader type")
         self._pixel_shader = pixel_shader
         self._hidden = False
         self._x = x
         self._y = y
         if not isinstance(pixel_shader, (ColorConverter, Palette)):
             raise ValueError("Unsupported Pixel Shader type")
         self._pixel_shader = pixel_shader
         self._hidden = False
         self._x = x
         self._y = y
-        self._width = width # Number of Tiles Wide
-        self._height = height # Number of Tiles High
+        self._width = width  # Number of Tiles Wide
+        self._height = height  # Number of Tiles High
         if tile_width is None:
             tile_width = bitmap_width
         if tile_height is None:
         if tile_width is None:
             tile_width = bitmap_width
         if tile_height is None:
@@ -872,40 +879,50 @@ class TileGrid:
         if bitmap_height % tile_height != 0:
             raise ValueError("Tile height must exactly divide bitmap height")
         self._tile_height = tile_height
         if bitmap_height % tile_height != 0:
             raise ValueError("Tile height must exactly divide bitmap height")
         self._tile_height = tile_height
+        if not 0 <= default_tile <= 255:
+            raise ValueError("Default Tile is out of range")
         self._tiles = (self._width * self._height) * [default_tile]
 
     def _fill_area(self, buffer):
         """Draw onto the image"""
         self._tiles = (self._width * self._height) * [default_tile]
 
     def _fill_area(self, buffer):
         """Draw onto the image"""
-        print("Drawing TileGrid")
         if self._hidden:
             return
         if self._hidden:
             return
-        
-        image = Image.new("RGB", (self._width * self._tile_width, self._height * self._tile_height))
-        
-        for tile_x in range(self._width):
-            for tile_y in range(self._height):
+
+        image = Image.new(
+            "RGB", (self._width * self._tile_width, self._height * self._tile_height)
+        )
+
+        tile_count_x = self._bitmap.width // self._tile_width
+        tile_count_y = self._bitmap.height // self._tile_height
+
+        for tile_x in range(0, self._width):
+            for tile_y in range(0, self._height):
                 tile_index = self._tiles[tile_y * self._width + tile_x]
                 tile_index = self._tiles[tile_y * self._width + tile_x]
-                tile_index_x = tile_index % self._width
-                tile_index_y = tile_index // self._width
+                tile_index_x = tile_index % tile_count_x
+                tile_index_y = tile_index // tile_count_x
                 for pixel_x in range(self._tile_width):
                     for pixel_y in range(self._tile_height):
                         image_x = tile_x * self._tile_width + pixel_x
                         image_y = tile_y * self._tile_height + pixel_y
                         bitmap_x = tile_index_x * self._tile_width + pixel_x
                         bitmap_y = tile_index_y * self._tile_height + pixel_y
                 for pixel_x in range(self._tile_width):
                     for pixel_y in range(self._tile_height):
                         image_x = tile_x * self._tile_width + pixel_x
                         image_y = tile_y * self._tile_height + pixel_y
                         bitmap_x = tile_index_x * self._tile_width + pixel_x
                         bitmap_y = tile_index_y * self._tile_height + pixel_y
-                        pixel_color = self._pixel_shader[self._bitmap[bitmap_x, bitmap_y]]
-                        image.putpixel((image_x, image_y), pixel_color["rgb888"])
-
+                        pixel_color = self._pixel_shader[
+                            self._bitmap[bitmap_x, bitmap_y]
+                        ]
+                        if not pixel_color["transparent"]:
+                            image.putpixel((image_x, image_y), pixel_color["rgb888"])
+
+        # Apply transforms here
+        if self._tile_width == 6:
+            print("Putting at {}".format((self._x, self._y)))
         buffer.paste(image, (self._x, self._y))
         """
         Strategy
         ------------
         Draw on it
         buffer.paste(image, (self._x, self._y))
         """
         Strategy
         ------------
         Draw on it
-        Apply the palette
         Do any transforms or mirrors or whatever
         Paste into buffer at our x,y position
         """
         Do any transforms or mirrors or whatever
         Paste into buffer at our x,y position
         """
-        
 
     @property
     def hidden(self):
 
     @property
     def hidden(self):
@@ -994,7 +1011,7 @@ class TileGrid:
         elif ininstance(index, int):
             x = index % self._width
             y = index // self._width
         elif ininstance(index, int):
             x = index % self._width
             y = index // self._width
-        if x > width or y > self._height or index > len(self._tiles):
+        if x > width or y > self._height or index >= len(self._tiles):
             raise ValueError("Tile index out of bounds")
         if not 0 <= value <= 255:
             raise ValueError("Tile value out of bounds")
             raise ValueError("Tile index out of bounds")
         if not 0 <= value <= 255:
             raise ValueError("Tile value out of bounds")
diff --git a/fontio.py b/fontio.py
new file mode 100644 (file)
index 0000000..976c54c
--- /dev/null
+++ b/fontio.py
@@ -0,0 +1,64 @@
+"""
+`fontio`
+"""
+
+__version__ = "0.0.0-auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
+
+from displayio import Bitmap
+from PIL import ImageFont
+
+
+class BuiltinFont:
+    def __init__(self):
+        self._font = ImageFont.load_default()
+        ascii = ""
+        for character in range(0x20, 0x7F):
+            ascii += chr(character)
+        self._font.getmask(ascii)
+        bmp_size = self._font.getsize(ascii)
+        self._bitmap = Bitmap(bmp_size[0], bmp_size[1], 2)
+        ascii_mask = self._font.getmask(ascii, mode="1")
+        for x in range(bmp_size[0]):
+            for y in range(bmp_size[1]):
+                self._bitmap[x, y] = 1 if ascii_mask.getpixel((x, y)) else 0
+
+    def _get_glyph_index(self, charcode):
+        if 0x20 <= charcode <= 0x7E:
+            return charcode - 0x20
+
+    def get_bounding_box(self):
+        """Returns the maximum bounds of all glyphs in the font in a tuple of two values: width, height."""
+        return self._font.getsize("M")
+
+    def get_glyph(self, codepoint):
+        """Returns a `fontio.Glyph` for the given codepoint or None if no glyph is available."""
+        bounding_box = self._font.getsize(chr(codepoint))
+        return Glyph(
+            bitmap=self._bitmap,
+            tile_index=self._get_glyph_index(codepoint),
+            width=bounding_box[0],
+            height=bounding_box[1],
+            dx=0,
+            dy=0,
+            shift_x=0,
+            shift_y=0,
+        )
+
+    @property
+    def bitmap(self):
+        """Bitmap containing all font glyphs starting with ASCII and followed by unicode. Use `get_glyph` in most cases. This is useful for use with `displayio.TileGrid` and `terminalio.Terminal`.
+        """
+        return self._bitmap
+
+
+class Glyph:
+    def __init__(self, *, bitmap, tile_index, width, height, dx, dy, shift_x, shift_y):
+        self.bitmap = bitmap
+        self.width = width
+        self.height = height
+        self.dx = dx
+        self.dy = dy
+        self.shift_x = shift_x
+        self.shift_y = shift_y
+        self.tile_index = tile_index
index f73f6676dd586673500045003dd22a600894b939..b312fef411d842bc9cd11ad71fa008e1e46d64ab 100644 (file)
@@ -6,9 +6,9 @@ __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
 import sys
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
 import sys
-from PIL import ImageFont
+import fontio
 
 
-FONT = ImageFont.load_default()
+FONT = fontio.BuiltinFont()
 
 # sys.stdout = open('out.dat', 'w')
 
 
 # sys.stdout = open('out.dat', 'w')