Top

rstem.led_matrix module

#!/usr/bin/env python3
#
# Copyright (c) 2014, Scott Silver Labs, LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import os
import re
import time
from . import led_driver     # c extension that controls led matrices and contains framebuffer
import copy
import subprocess

# global variables for use
BITS_PER_PIXEL = 4     # 4 bits to represent color
DIM_OF_MATRIX = 8     # 8x8 led matrix elements
initialized = False   # flag to indicate if LED has been initialized
spi_initialized = False  # flag to indicate if SPI bus as been initialized
width = 0    #: The width of the LED matrix grid
height = 0   #: The height of the LED matrix grid
container_math_coords = True


def _init_check():
    """Checks that init_matrices has been called and throws an error if not
    @raise RuntimeError: If matrices have not been initialized."""
    global initialized
    if not initialized:
        raise RuntimeError("Matrices must be initialized first.")
    

def _valid_color(color):
    """Checks if given color is number between 0-16 if an int or 0-f, or - if string
    @param color: A color to check that is valid
    @type color: string or int
    """
    if type(color) == int:
        if color < 0x0 or color > 0x10:
            return False
        return True
    elif type(color) == str:
        if not re.match(r'^[A-Za-z0-9-]$', color):
            return False
        return True
    return False


def _convert_color(color):
    """Converts the given color to an int.
    @param color: A color to be converted
    @type color: string or int
    
    @raise ValueError: Fails L{_valid_color} check
    
    @return: Either the same int back again if color was an int or the int of a converted string
    @rtype: int
    """
    if not _valid_color(color):
        raise ValueError("Invalid Color: must be a string between 0-f or '-'" +
                         " or a number 0-16 with 16 being transparent")
    if type(color) == int:
        return color
    elif type(color) == str:
        if color == '-':
            return 0x10
        return int(color, 16)
    raise RuntimeError("Invalid color")


def _convert_to_std_coords(x, y):
    """Converts given math coordinates to standard programming coordinates
    @param x: x coordinate in math coordinates
    @param y: y coordinate in math coordinates
    @rtype: tuple
    @return: (x,y) coordinates in standard programming coordinates"""
    return x, (height - 1 - y)

    
def display_on_terminal():
    """Toggles on and off the terminal display of the led matrix on show()"""
    led_driver.display_on_terminal()


def init_matrices(mat_list=[(0, 0, 0)], math_coords=True, spi_speed=125000, spi_port=0):
    """Creates a chain of led matrices set at particular offsets into the frame buffer
    The order of the led matrices in the list indicate the order they are
    physically hooked up with the first one connected to Pi.
    
    @param mat_list: list of tuples that contains led matrix and offset
        (in math coordinates if math_coords==True, else in programmer coordinate if math_coords==False)
        ex: [(0,0,0),(7,0,90)]
    @type mat_list: list of size 2 or 3 tuples (mix or match)
    @param math_coords: True to use math coordinates, False to use programming coordinates
    @type math_coords: Boolean
    """
    global initialized
    global width
    global height
    global container_math_coords
    if initialized: # free previous memory before attempting to do it again
        cleanup()
                
    width = max([matrix[0] for matrix in mat_list]) + DIM_OF_MATRIX
    height = max([matrix[1] for matrix in mat_list]) + DIM_OF_MATRIX
    container_math_coords = math_coords
    if container_math_coords:
        for i in range(len(mat_list)):
            # convert y's to be standard programming coordinates
            # and also move origin from bottom left to top left of matrix
            if len(mat_list[i]) > 2:
                mat_list[i] = (mat_list[i][0], (height-1 - mat_list[i][1]) - (DIM_OF_MATRIX-1), mat_list[i][2])
            else:
                mat_list[i] = (mat_list[i][0], (height-1 - mat_list[i][1]) - (DIM_OF_MATRIX-1))
    led_driver.init_matrices(mat_list, len(mat_list), width, height) # flatten out tuple
        
    # initialize spi bus
    global spi_initialized
    if not spi_initialized:
        led_driver.init_SPI(spi_speed, spi_port)
        spi_initialized = True
    
    initialized = True


    
def init_grid(num_rows=None, num_cols=None, angle=0, math_coords=True, spi_speed=125000, spi_port=0):
    """Initiallizes led matrices in a grid pattern with either a given number
    of rows and columns.
    If num_rows and num_cols is not given, it will detect the number of matrices you have
    and automatically set up a grid pattern with a max number of columns of 4.
    
    @param num_rows: Number of rows the grid is in (when angle == 0)
    @param num_cols: Number of columns the gird is in (when angle == 0)
    @param math_coords: True to use math coordinates, False to use programming coordinates
    @type math_coords: Boolean
    @param angle: Angle to rotate the coordinate system once initialized. (must be 90 degree multiple)
    @type angle: int
    
    @raise ValueError: num_rows*num_cols != number of matrices
    @raise ValueError: angle is not a multiple of 90
    """


    # initialize spi bus right away because we need it for led_driver.detect()
    global spi_initialized
    if not spi_initialized:
        led_driver.init_SPI(spi_speed, spi_port)
        spi_initialized = True
    

    if num_cols is None:
        # num_rows, and num_cols are before rotation
        # auto detect number of columns if none given
        num_matrices = led_driver.detect()
        if num_rows is None:
            # if number of rows not given assume max of 4 columns per row
            for cols in reversed(range(5)):  # should never hit zero
                rows = float(num_matrices)/cols
                if rows.is_integer():
                    num_rows = int(rows)
                    num_cols = cols
                    break
        else:
            num_cols = int(num_matrices/num_rows)
            
        if num_cols*num_rows != num_matrices:  # safety check
          raise ValueError("Invalid number of rows and columns")
    
    elif num_rows is None:
        raise ValueError("If you are providing num_cols you must also provide num_rows.")
    

    if angle % 90 != 0:
        raise ValueError("Angle must be a multiple of 90.")
    angle = angle % 360
    
    mat_list = []
    if angle == 0:
        for row in range(num_rows): # increment through rows downward
            if row % 2 == 1:
                for column in range(num_cols-1,-1,-1):  # if odd increment right to left
                    mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 180))  # upside-down
            else:
                for column in range(num_cols): # if even, increment left to right
                    mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 0))  # right side up
    elif angle == 90: # 90 degrees clockwise
        for row in range(num_rows): 
            if row % 2 == 1:
                for column in range(num_cols-1,-1,-1): # if odd, increment downward
                    mat_list.append(((num_rows-row - 1)*DIM_OF_MATRIX, column*DIM_OF_MATRIX, 270)) # 180 + 90
            else:
                for column in range(num_cols): # if even, increment upwards
                    mat_list.append(((num_rows-row - 1)*DIM_OF_MATRIX, column*DIM_OF_MATRIX, 90))  # 0 + 90
    elif angle == 180:
        for row in range(num_rows-1,-1,-1): # increment through rows upwards
            if row % 2 == 0:
                for column in range(num_cols-1,-1,-1):  # if even increment right to left
                    mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 180)) # 0 + 180
            else:
                for column in range(num_cols): # if even increment left to right
                    mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 0)) # 180 + 180
    elif angle == 270: # 90 degrees counter-clockwise
        for row in range(num_rows): # increment columns right to left
            if row % 2 == 1:
                for column in range(num_cols-1,-1,-1): # if odd increment through rows upwards
                    mat_list.append((row*DIM_OF_MATRIX, (num_cols - 1- column)*DIM_OF_MATRIX, 90)) # 180 - 90
            else:
                for column in range(num_cols): # increment through rows downwards
                    mat_list.append((row*DIM_OF_MATRIX, (num_cols - 1 - column)*DIM_OF_MATRIX, 270)) # 0 - 90 = 270
                  
    global container_math_coords
    init_matrices(mat_list, math_coords=False, spi_speed=spi_speed, spi_port=spi_port)
    container_math_coords = math_coords

def show():
    """Shows the current framebuffer on the display.
    Tells the led_driver to send framebuffer to SPI port.    
    Refreshes the display using current framebuffer.
    
    @rtype: int
    @returns: 1 on success
    """
    _init_check()
    return led_driver.flush()

def cleanup():
    """Unintializes matrices and frees all memory. 
    Should be called at the end of a program to avoid memory leaks.
    Also, clears the display.
    """
    global initialized
    global spi_initialized
    if initialized:
        led_driver.fill(0x0)
        led_driver.flush()
        led_driver.shutdown_matrices()
        initialized = False
        spi_initialized = False

def fill(color=0xF):
    """Fills the framebuffer with the given color.
    @param color: Color to fill the entire display with
    @type color: int or string (0-F or 16 or '-' for transparent)
    """
    _init_check()
    led_driver.fill(_convert_color(color))

    
def erase():
    """Clears display"""
    fill(0)

def point(x, y=None, color=0xF):
    """Sends point to the framebuffer.
    @note: Will not display the point until show() is called.
    
    @param x, y: Coordinates to place point
    @type x, y: (int, int)
    @param color: Color to display at point
    @type color: int or string (0-F or 16 or '-' for transparent)
    
    @rtype: int
    @returns: 1 on success
    """
    _init_check()
    color = _convert_color(color)
    if color < 16:   # don't do anything if transparent
        global width
        global height
        # If y is not given, then x is a tuple of the point
        if y is None and type(x) is tuple:
            x, y = x
        if x < 0 or x >= width or y < 0 or y >= height:
            return
        if container_math_coords:
            x, y = _convert_to_std_coords(x, y)
        return led_driver.point(int(x), int(y), color)

def rect(origin, dimensions, fill=True, color=0xF):
    """Creates a rectangle from start point using given dimensions
    
    @param origin: The bottom left corner of rectange (if math_coords == True).
        The top left corner of rectangle (if math_coords == False)
    @type origin: (x,y) tuple
    @param dimensions: width and height of rectangle
    @type dimensions: (width, height) tuple
    @param fill: Whether to fill the rectangle or make it hollow
    @type fill: boolean
    @param color: Color to display at point
    @type color: int or string (0-F or 16 or '-' for transparent)
    """
    x, y = origin
    width, height = dimensions

    if fill:
        for x_offset in range(width):
            line((x + x_offset, y), (x + x_offset, y + height - 1), color)
    else:
        line((x, y), (x, y + height - 1), color)
        line((x, y + height - 1), (x + width - 1, y + height - 1), color)
        line((x + width - 1, y + height - 1), (x + width - 1, y), color)
        line((x + width - 1, y), (x, y), color)
    


def _sign(n):
    return 1 if n >= 0 else -1

def line(point_a, point_b, color=0xF):
    """Create a line from point_a to point_b.
    Uses Bresenham's Line Algorithm U{http://en.wikipedia.org/wiki/Bresenham's_line_algorithm}
    @type point_a, point_b: (x,y) tuple
    @param color: Color to display at point
    @type color: int or string (0-F or 16 or '-' for transparent)
    """
    x1, y1 = point_a
    x2, y2 = point_b
    dx = abs(x2 - x1)
    dy = abs(y2 - y1)
    sx = 1 if x1 < x2 else -1
    sy = 1 if y1 < y2 else -1
    err = dx - dy
    while True:
        point(x1, y1, color)
        if (x1 == x2 and y1 == y2) or x1 >= width or y1 >= height:
            break
        e2 = 2*err
        if e2 > -dy:
            err -= dy
            x1 += sx
        if e2 < dx:
            err += dx
            y1 += sy
            
def _line_fast(point_a, point_b, color=0xF):
    """A faster c implementation of line. Use if you need the speed."""
    if container_math_coords:
        point_a = _convert_to_std_coords(*point_a)
        point_b = _convert_to_std_coords(*point_b)
    led_driver.line(point_a[0], point_a[1], point_b[0], point_b[1], _convert_color(color))


def text(text, origin=(0, 0), crop_origin=(0, 0), crop_dimensions=None, font_name="small", font_path=None):
    """Sets given string to be displayed on the led matrix
        
    Example:
        >>> text("Hello World", (0,0), (0,1), (0,5))
        >>> show()
        Displays only part of the first vertical line in 'H'
        
    @param origin, crop_origin, crop_dimensions: See L{sprite}
        
    @param text: text to display
    @param text: string or L{LEDText}
    @param font_name: Font folder (or .font) file to use as font face
    @type font_name: string
    @param font_path: Directory to use to look for fonts. If None it will used default directory in dist-packages
    @type font_path: string
    
    @returns: LEDText sprite object used to create text
    @rtype: L{LEDText}
    """
    if type(text) == str:
        text = LEDText(text, font_name=font_name, font_path=font_path)
    elif type(text) != LEDText and type(text) != LEDSprite:
        raise ValueError("Invalid text")
    sprite(text, origin, crop_origin, crop_dimensions)
    return text


def sprite(sprite, origin=(0,0), crop_origin=(0,0), crop_dimensions=None):
    """Sets given sprite into the framebuffer.
    
    @param origin: Bottom left position to diplay text (top left if math_coords == False)
    @type origin: (x,y) tuple
    @param crop_origin: Position to crop into the sprite relative to the origin
    @type crop_origin: (x,y) tuple
    @param crop_dimensions: x and y distance from the crop_origin to display
        - Keep at None to not crop and display sprite all the way to top right corner (bottom right for math_coords == False)
    @type crop_dimensions: (x,y) tuple
    """
    if type(sprite) == str:
        sprite = LEDSprite(sprite)
    elif type(sprite) != LEDText and type(sprite) != LEDSprite:
        raise ValueError("Invalid sprite")
    
    _init_check()
    global width
    global height
    
    x_pos, y_pos = origin
    x_crop, y_crop = crop_origin
    
    # set up offset
    if x_crop < 0 or y_crop < 0:
        raise ValueError("crop_origin must be positive numbers")
    
    # set up crop
    if crop_dimensions is None:
        x_crop_dim, y_crop_dim = (sprite.width, sprite.height)   # no cropping
    else:
        x_crop_dim, y_crop_dim = crop_dimensions
        if x_crop_dim < 0 or y_crop_dim < 0:
            raise ValueError("crop_dimensions must be positive numbers")
        
    # set up start position
    x_start = x_pos + x_crop
    y_start = y_pos + y_crop 
    
    if x_start >= width or y_start >= height:
        return
        
    # set up end position
    x_end = min(x_pos + x_crop + x_crop_dim, width, x_pos + sprite.width)
    y_end = min(y_pos + y_crop + y_crop_dim, height, y_pos + sprite.height)
    
    # iterate through sprite and set points to led_driver
    y = max(y_start,0)
    while y < y_end:
        x = max(x_start, 0)
        while x < x_end:
            x_sprite = x - x_start + x_crop
            y_sprite = y - y_start + y_crop
            x_sprite = int(x_sprite)
            y_sprite = int(y_sprite)
            if container_math_coords:
                y_sprite = sprite.height - 1 - y_sprite
            point((x, y), color=sprite.bitmap[y_sprite][x_sprite])
            x += 1
        y += 1
        
def frame(numpy_bitmap):
    """Sends the entire frame (represented as a numpy bitmap) to the led matrix.
    
    @param numpy_bitmap: A ndarray representing the entire framebuffer to display.
    @type numpy_bitmap: numpy ndarray
    @note: bitmap dimensions must be the same as the dimensions of the container (non rotated).
    """
    led_driver.frame(numpy_bitmap)


class LEDSprite(object):
    """Allows the creation of a LED Sprite that is defined in a text file.
    @note: The text file must only contain hex numbers 0-9, a-f, A-F, or - (dash)
    @note: The hex number indicates pixel color and "-" indicates a transparent pixel
    """
    def __init__(self, filename=None, height=0, width=0, color=0x0):
        """Creates a L{LEDSprite} object from the given .spr file or image file or creates an empty sprite of given
        height and width if filename == None.
        
        @param filename: The full path location of a .spr sprite file or image file
        @type: string
        @param height: The height of given sprite if creating an empty sprite or want to resize a sprite from and image file.
        @type height: int
        @param width: The width of given sprite if creating an empty sprite or want to resize a sprite from and image file.
        @type width: int
        @param color: Color to display at point
        @type color: int or string (0-F or 16 or '-' for transparent)
        """
        bitmap = []
        bitmap_width = 0  # keep track of width and height
        bitmap_height = 0
        if filename is not None:
            filename = filename.strip()
            self.filename = filename    
            # get filetype
            proc = subprocess.Popen("file " + str(filename), shell=True, stdout=subprocess.PIPE)
            output, errors = proc.communicate()
            if type(output) == bytes:  # convert from byte to a string (happens if using python3)
                output = output.decode() 
            if errors is not None or (output.find("ERROR") != -1):
                raise IOError(output)
                
            if output.find("text") != -1 or output.find("FORTRAN") != -1:  # file is a text file
                if filename[-4:] != ".spr":
                    raise ValueError("Filename must have '.spr' extension.")
                f = open(filename, 'r')
                for line in f:
                    leds = [_convert_color(char) for char in line.split()]
                    # Determine if widths are consistent
                    if bitmap_width != 0:
                        if len(leds) != bitmap_width:
                            raise ValueError("Sprite has different widths")
                    else:
                        bitmap_width = len(leds)
                    bitmap.append(leds)
                    bitmap_height += 1
                f.close()
                
            elif output.find("image") != -1:  # file is an image file
                import scipy.misc, numpy, sys
                if sys.version_info[0] > 2:
                    raise ValueError("As of now, only python 2 supports images to sprites.")
                # if no height or width given try to fill as much of the display
                # with the image without stretching it
                if height <= 0 or width <= 0:
                    from PIL import Image
                    im = Image.open(filename)
                    im_width, im_height = im.size
                    bitmap_height = min(height, im_height)
                    bitmap_width = min(width, bitmap_height * (im_width / im_height))
                else:
                    bitmap_height = height
                    bitmap_width = width
                # pixelize and resize image with scipy
                image = scipy.misc.imread(filename, flatten=True)
                con_image = scipy.misc.imresize(image, (bitmap_width, bitmap_height), interp='cubic')
                con_image = numpy.transpose(con_image)  # convert from column-wise to row-wise
                con_image = numpy.fliplr(con_image)  # re-orient the image
                con_image = numpy.rot90(con_image, k=1)
                bitmap = [[int(pixel*16/255) for pixel in line] for line in con_image]  # convert to bitmap
            else:
                raise IOError("Unsupported filetype")
        else:
            # create an empty sprite of given height and width
            bitmap = [[color for i in range(width)] for j in range(height)]
            bitmap_height = height
            bitmap_width = width

        self.bitmap = bitmap
        # self.height = bitmap_height
        # self.width = bitmap_width

    @property
    def width(self):
        return len(self.bitmap[0])

    @property
    def height(self):
        return len(self.bitmap)

    def append(self, sprite):
        """Appends given sprite to the right of itself.
        
        @param sprite: sprite to append
        @type sprite: L{LEDSprite}
        @note: height of given sprite must be <= to itself otherwise will be truncated
        """
        for i, line in enumerate(self.bitmap):
            if i >= sprite.height:
                # fill in with transparent pixels
                tran_line = [0x10 for j in range(sprite.width)]
                self.bitmap[i] = sum([line, tran_line], [])
            else:
                self.bitmap[i] = sum([line, sprite.bitmap[i]], [])
        # update size
        # self.width += sprite.width

    def set_pixel(self, point, color=0xF):
        """Sets given color to given x and y coordinate in sprite

        @param point: point relative to sprite to set point
        @type point: (x,y)
        @param color: Color to display at point
        @type color: int or string (0-F or 16 or '-' for transparent)
        
        @return: None if coordinate is not valid
        """
        x, y = point
        if x >= self.width or y >= self.height or x < 0 or y < 0:
            return None
        self.bitmap[y][x] = _convert_color(color)

    def get_pixel(self, x, y):
        """
        @rtype: int
        @returns: int of color at given origin or None
        """
        if x >= self.width or y >= self.height or x < 0 or y < 0:
            return None
        return self.bitmap[y][x]

    
    def save_to_file(self, filename):
        """Saves sprite bitmap to given .spr file. 
        
        @param filename: relative filename path
        @type filename: string
        @note: It will truncate filename if it already exists.
        """
        filename = filename.strip()
        if filename[-4:] != ".spr":
            raise ValueError("Filename must have '.spr' extension.")
        f = open(filename, 'w')
        for line in self.bitmap:
            for pixel in line:
                if pixel > 15:
                    f.write("- ")
                else:
                    f.write(hex(pixel)[2] + " ")
            f.write("\n")
        f.close()
        
    def rotate(self, angle=90):
        """Rotates sprite at 90 degree intervals. 
        
        @returns: self
        @rtype: L{LEDSprite}
        
        @param angle: angle to rotate self in an interval of 90 degrees
        @type angle: int
        
        @returns: self
        @rtype: L{LEDSprite}
        @raises ValueError: If angle is not multiple of 90
        @note: If no angle given, will rotate sprite 90 degrees.
        """
        if angle % 90 != 0:
            raise ValueError("Angle must be a multiple of 90.")
            
        angle = angle % 360    
        if angle == 90:
            bitmap = []
            for i in range(self.width):
                bitmap.append([row[i] for row in reversed(self.bitmap)])
            self.bitmap = bitmap
            # swap height and width
            # temp = self.width
            # self.width = self.height
            # self.height = temp
            
        elif angle == 180:
            self.bitmap.reverse()
            for row in self.bitmap:
                row.reverse()
                
        elif angle == 270:
            bitmap = []
            for i in range(self.width-1,-1,-1):
                bitmap.append([row[i] for row in self.bitmap])
            self.bitmap = bitmap
            # swap height and width
            # temp = self.width
            # self.width = self.height
            # self.height = temp
        return self
        
    def rotated(self, angle=90):
        """Same as L{rotate} only it returns a copy of the rotated sprite
        and does not affect the original.
        @returns: Rotated sprite
        @rtype: L{LEDSprite}
        """
        sprite_copy = copy.deepcopy(self)
        sprite_copy.rotate(angle)
        return sprite_copy
        
    def copy(self):
        """Copies sprite
        @returns: A copy of sprite without affecting original sprite
        @rtype: L{LEDSprite}
        """
        return copy.deepcopy(self)
        
    def invert(self):
        """Inverts the sprite.
        @returns: self
        @rtype: L{LEDSprite}
        """
        for y, line in enumerate(self.bitmap):
            for x, pixel in enumerate(line):
                if pixel < 16:
                    self.bitmap[y][x] = 15 - pixel
                    
    def inverted(self):
        """Same as L{invert} only it returns a copy of the inverted sprite
        and does not affect the original.
        @returns: Inverted sprite
        @rtype: L{LEDSprite}
        """
        sprite_copy = copy.deepcopy(self)
        sprite_copy.invert()
        return sprite_copy
        
    def flip_horizontal(self):
        """Flips the sprite horizontally.
        @returns: self
        @rtype: L{LEDSprite}
        """
        self.bitmap.reverse()
        return self
        
    def flipped_horizontal(self):
        """Same as L{flip_horizontal} only it returns a copy of the flipped sprite
        and does not affect the original.
        @returns: sprite flipped horizontally
        @rtype: L{LEDSprite}
        """
        sprite_copy = copy.deepcopy(self)
        sprite_copy.flip_horizontal()
        return sprite_copy
        
        
    def flip_vertical(self):
        """Flips the sprite vertically.
        @returns: self
        @rtype: L{LEDSprite}
        """
        for line in self.bitmap:
            line.reverse()
        return self
        
    def flipped_vertical(self):
        """Same as L{flip_vertical} only it returns a copy of the flipped sprite
        and does not affect the original.
        @returns: sprite flipped vertically
        @rtype: L{LEDSprite}
        """
        sprite_copy = copy.deepcopy(self)
        sprite_copy.flip_vertical()
        return sprite_copy


def _char_to_sprite(char, font_path):
    """Converts given character to a sprite.
    
    @param char: character to convert (must be of length == 1)
    @type char: string
    @param font_path: Relative location of font face to use.
    @type font_path: string
    @rtype: L{LEDSprite}
    """
    if not (type(char) == str and len(char) == 1):
        raise ValueError("Not a character")
    orig_font_path = font_path
    if char.isdigit():
        font_path = os.path.join(font_path, "numbers", char + ".spr")
    elif char.isupper():
        font_path = os.path.join(font_path, "upper", char + ".spr")
    elif char.islower():
        font_path = os.path.join(font_path, "lower", char + ".spr")
    elif char.isspace():
        font_path = os.path.join(font_path, "space.spr")
    else:
        font_path = os.path.join(font_path, "misc", str(ord(char)) + ".spr")
        
    if os.path.isfile(font_path):
        return LEDSprite(font_path)
    else:
        return LEDSprite(os.path.join(orig_font_path, "unknown.spr"))
        
        

class LEDText(LEDSprite):
    """A L{LEDSprite} object of a piece of text."""
    def __init__(self, message, char_spacing=1, font_name="small", font_path=None):
        """Creates a text sprite of the given string
        This object can be used the same way a sprite is useds
        
        @param char_spacing: number pixels between characters
        @type char_spacing: int
        @param font_name: Font folder (or .font) file to use as font face
        @type font_name: string
        @param font_path: Directory to use to look for fonts. If None it will used default directory in dist-packages
        @type font_path: string
        """
        if font_path is None: # if none, set up default font location
            this_dir, this_filename = os.path.split(__file__)
            # only use font_name if no custom font_path was given
            font_path = os.path.join(this_dir, "font")
            
        if not os.path.isdir(font_path):
            raise IOError("Font path does not exist.")
        orig_font_path = font_path

        # attach font_name to font_path
        font_path = os.path.join(font_path, font_name)
        
        # if font subdirectory doesn exist, attempt to open a .font file
        if not os.path.isdir(font_path):
            f = open(os.path.join(orig_font_path, font_name + ".font"), 'r')
            font_path = os.path.join(orig_font_path, f.read().strip())
            f.close()

        message = message.strip()
        if len(message) == 0:
            super(LEDSprite, self).__init__()
            return

        # start with first character as intial sprite object
        init_sprite = _char_to_sprite(message[0], font_path)
        # get general height and width of characters

        if len(message) > 1:
            # append other characters to initial sprite
            for char in message[1:]:
                # add character spacing
                init_sprite.append(LEDSprite(height=init_sprite.height, width=char_spacing, color=0x10))
                # now add next character
                sprite = _char_to_sprite(char, font_path)
                if sprite.height != init_sprite.height:
                    raise ValueError("Height of character sprites must all be the same.")
                # append
                init_sprite.append(sprite)

        self.bitmap = init_sprite.bitmap
        
        
# run demo program if run by itself
def _main():
    init_matrices()
    while 1:
        for x in range(8):
            for y in range(8):
                point(x, y)
                show()
                time.sleep(0.5);
                erase()

if __name__ == "__main__":
    _main()

Module variables

var BITS_PER_PIXEL

var DIM_OF_MATRIX

var container_math_coords

var height

var initialized

var spi_initialized

var width

Functions

def cleanup(

)

Unintializes matrices and frees all memory. Should be called at the end of a program to avoid memory leaks. Also, clears the display.

def cleanup():
    """Unintializes matrices and frees all memory. 
    Should be called at the end of a program to avoid memory leaks.
    Also, clears the display.
    """
    global initialized
    global spi_initialized
    if initialized:
        led_driver.fill(0x0)
        led_driver.flush()
        led_driver.shutdown_matrices()
        initialized = False
        spi_initialized = False

def display_on_terminal(

)

Toggles on and off the terminal display of the led matrix on show()

def display_on_terminal():
    """Toggles on and off the terminal display of the led matrix on show()"""
    led_driver.display_on_terminal()

def erase(

)

Clears display

def erase():
    """Clears display"""
    fill(0)

def fill(

color=15)

Fills the framebuffer with the given color. @param color: Color to fill the entire display with @type color: int or string (0-F or 16 or '-' for transparent)

def fill(color=0xF):
    """Fills the framebuffer with the given color.
    @param color: Color to fill the entire display with
    @type color: int or string (0-F or 16 or '-' for transparent)
    """
    _init_check()
    led_driver.fill(_convert_color(color))

def frame(

numpy_bitmap)

Sends the entire frame (represented as a numpy bitmap) to the led matrix.

@param numpy_bitmap: A ndarray representing the entire framebuffer to display. @type numpy_bitmap: numpy ndarray @note: bitmap dimensions must be the same as the dimensions of the container (non rotated).

def frame(numpy_bitmap):
    """Sends the entire frame (represented as a numpy bitmap) to the led matrix.
    
    @param numpy_bitmap: A ndarray representing the entire framebuffer to display.
    @type numpy_bitmap: numpy ndarray
    @note: bitmap dimensions must be the same as the dimensions of the container (non rotated).
    """
    led_driver.frame(numpy_bitmap)

def init_grid(

num_rows=None, num_cols=None, angle=0, math_coords=True, spi_speed=125000, spi_port=0)

Initiallizes led matrices in a grid pattern with either a given number of rows and columns. If num_rows and num_cols is not given, it will detect the number of matrices you have and automatically set up a grid pattern with a max number of columns of 4.

@param num_rows: Number of rows the grid is in (when angle == 0) @param num_cols: Number of columns the gird is in (when angle == 0) @param math_coords: True to use math coordinates, False to use programming coordinates @type math_coords: Boolean @param angle: Angle to rotate the coordinate system once initialized. (must be 90 degree multiple) @type angle: int

@raise ValueError: num_rows*num_cols != number of matrices @raise ValueError: angle is not a multiple of 90

def init_grid(num_rows=None, num_cols=None, angle=0, math_coords=True, spi_speed=125000, spi_port=0):
    """Initiallizes led matrices in a grid pattern with either a given number
    of rows and columns.
    If num_rows and num_cols is not given, it will detect the number of matrices you have
    and automatically set up a grid pattern with a max number of columns of 4.
    
    @param num_rows: Number of rows the grid is in (when angle == 0)
    @param num_cols: Number of columns the gird is in (when angle == 0)
    @param math_coords: True to use math coordinates, False to use programming coordinates
    @type math_coords: Boolean
    @param angle: Angle to rotate the coordinate system once initialized. (must be 90 degree multiple)
    @type angle: int
    
    @raise ValueError: num_rows*num_cols != number of matrices
    @raise ValueError: angle is not a multiple of 90
    """


    # initialize spi bus right away because we need it for led_driver.detect()
    global spi_initialized
    if not spi_initialized:
        led_driver.init_SPI(spi_speed, spi_port)
        spi_initialized = True
    

    if num_cols is None:
        # num_rows, and num_cols are before rotation
        # auto detect number of columns if none given
        num_matrices = led_driver.detect()
        if num_rows is None:
            # if number of rows not given assume max of 4 columns per row
            for cols in reversed(range(5)):  # should never hit zero
                rows = float(num_matrices)/cols
                if rows.is_integer():
                    num_rows = int(rows)
                    num_cols = cols
                    break
        else:
            num_cols = int(num_matrices/num_rows)
            
        if num_cols*num_rows != num_matrices:  # safety check
          raise ValueError("Invalid number of rows and columns")
    
    elif num_rows is None:
        raise ValueError("If you are providing num_cols you must also provide num_rows.")
    

    if angle % 90 != 0:
        raise ValueError("Angle must be a multiple of 90.")
    angle = angle % 360
    
    mat_list = []
    if angle == 0:
        for row in range(num_rows): # increment through rows downward
            if row % 2 == 1:
                for column in range(num_cols-1,-1,-1):  # if odd increment right to left
                    mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 180))  # upside-down
            else:
                for column in range(num_cols): # if even, increment left to right
                    mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 0))  # right side up
    elif angle == 90: # 90 degrees clockwise
        for row in range(num_rows): 
            if row % 2 == 1:
                for column in range(num_cols-1,-1,-1): # if odd, increment downward
                    mat_list.append(((num_rows-row - 1)*DIM_OF_MATRIX, column*DIM_OF_MATRIX, 270)) # 180 + 90
            else:
                for column in range(num_cols): # if even, increment upwards
                    mat_list.append(((num_rows-row - 1)*DIM_OF_MATRIX, column*DIM_OF_MATRIX, 90))  # 0 + 90
    elif angle == 180:
        for row in range(num_rows-1,-1,-1): # increment through rows upwards
            if row % 2 == 0:
                for column in range(num_cols-1,-1,-1):  # if even increment right to left
                    mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 180)) # 0 + 180
            else:
                for column in range(num_cols): # if even increment left to right
                    mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 0)) # 180 + 180
    elif angle == 270: # 90 degrees counter-clockwise
        for row in range(num_rows): # increment columns right to left
            if row % 2 == 1:
                for column in range(num_cols-1,-1,-1): # if odd increment through rows upwards
                    mat_list.append((row*DIM_OF_MATRIX, (num_cols - 1- column)*DIM_OF_MATRIX, 90)) # 180 - 90
            else:
                for column in range(num_cols): # increment through rows downwards
                    mat_list.append((row*DIM_OF_MATRIX, (num_cols - 1 - column)*DIM_OF_MATRIX, 270)) # 0 - 90 = 270
                  
    global container_math_coords
    init_matrices(mat_list, math_coords=False, spi_speed=spi_speed, spi_port=spi_port)
    container_math_coords = math_coords

def init_matrices(

mat_list=[(0, 0, 0)], math_coords=True, spi_speed=125000, spi_port=0)

Creates a chain of led matrices set at particular offsets into the frame buffer The order of the led matrices in the list indicate the order they are physically hooked up with the first one connected to Pi.

@param mat_list: list of tuples that contains led matrix and offset (in math coordinates if math_coords==True, else in programmer coordinate if math_coords==False) ex: [(0,0,0),(7,0,90)] @type mat_list: list of size 2 or 3 tuples (mix or match) @param math_coords: True to use math coordinates, False to use programming coordinates @type math_coords: Boolean

def init_matrices(mat_list=[(0, 0, 0)], math_coords=True, spi_speed=125000, spi_port=0):
    """Creates a chain of led matrices set at particular offsets into the frame buffer
    The order of the led matrices in the list indicate the order they are
    physically hooked up with the first one connected to Pi.
    
    @param mat_list: list of tuples that contains led matrix and offset
        (in math coordinates if math_coords==True, else in programmer coordinate if math_coords==False)
        ex: [(0,0,0),(7,0,90)]
    @type mat_list: list of size 2 or 3 tuples (mix or match)
    @param math_coords: True to use math coordinates, False to use programming coordinates
    @type math_coords: Boolean
    """
    global initialized
    global width
    global height
    global container_math_coords
    if initialized: # free previous memory before attempting to do it again
        cleanup()
                
    width = max([matrix[0] for matrix in mat_list]) + DIM_OF_MATRIX
    height = max([matrix[1] for matrix in mat_list]) + DIM_OF_MATRIX
    container_math_coords = math_coords
    if container_math_coords:
        for i in range(len(mat_list)):
            # convert y's to be standard programming coordinates
            # and also move origin from bottom left to top left of matrix
            if len(mat_list[i]) > 2:
                mat_list[i] = (mat_list[i][0], (height-1 - mat_list[i][1]) - (DIM_OF_MATRIX-1), mat_list[i][2])
            else:
                mat_list[i] = (mat_list[i][0], (height-1 - mat_list[i][1]) - (DIM_OF_MATRIX-1))
    led_driver.init_matrices(mat_list, len(mat_list), width, height) # flatten out tuple
        
    # initialize spi bus
    global spi_initialized
    if not spi_initialized:
        led_driver.init_SPI(spi_speed, spi_port)
        spi_initialized = True
    
    initialized = True

def line(

point_a, point_b, color=15)

Create a line from point_a to point_b. Uses Bresenham's Line Algorithm U{http://en.wikipedia.org/wiki/Bresenham's_line_algorithm} @type point_a, point_b: (x,y) tuple @param color: Color to display at point @type color: int or string (0-F or 16 or '-' for transparent)

def line(point_a, point_b, color=0xF):
    """Create a line from point_a to point_b.
    Uses Bresenham's Line Algorithm U{http://en.wikipedia.org/wiki/Bresenham's_line_algorithm}
    @type point_a, point_b: (x,y) tuple
    @param color: Color to display at point
    @type color: int or string (0-F or 16 or '-' for transparent)
    """
    x1, y1 = point_a
    x2, y2 = point_b
    dx = abs(x2 - x1)
    dy = abs(y2 - y1)
    sx = 1 if x1 < x2 else -1
    sy = 1 if y1 < y2 else -1
    err = dx - dy
    while True:
        point(x1, y1, color)
        if (x1 == x2 and y1 == y2) or x1 >= width or y1 >= height:
            break
        e2 = 2*err
        if e2 > -dy:
            err -= dy
            x1 += sx
        if e2 < dx:
            err += dx
            y1 += sy

def point(

x, y=None, color=15)

Sends point to the framebuffer. @note: Will not display the point until show() is called.

@param x, y: Coordinates to place point @type x, y: (int, int) @param color: Color to display at point @type color: int or string (0-F or 16 or '-' for transparent)

@rtype: int @returns: 1 on success

def point(x, y=None, color=0xF):
    """Sends point to the framebuffer.
    @note: Will not display the point until show() is called.
    
    @param x, y: Coordinates to place point
    @type x, y: (int, int)
    @param color: Color to display at point
    @type color: int or string (0-F or 16 or '-' for transparent)
    
    @rtype: int
    @returns: 1 on success
    """
    _init_check()
    color = _convert_color(color)
    if color < 16:   # don't do anything if transparent
        global width
        global height
        # If y is not given, then x is a tuple of the point
        if y is None and type(x) is tuple:
            x, y = x
        if x < 0 or x >= width or y < 0 or y >= height:
            return
        if container_math_coords:
            x, y = _convert_to_std_coords(x, y)
        return led_driver.point(int(x), int(y), color)

def rect(

origin, dimensions, fill=True, color=15)

Creates a rectangle from start point using given dimensions

@param origin: The bottom left corner of rectange (if math_coords == True). The top left corner of rectangle (if math_coords == False) @type origin: (x,y) tuple @param dimensions: width and height of rectangle @type dimensions: (width, height) tuple @param fill: Whether to fill the rectangle or make it hollow @type fill: boolean @param color: Color to display at point @type color: int or string (0-F or 16 or '-' for transparent)

def rect(origin, dimensions, fill=True, color=0xF):
    """Creates a rectangle from start point using given dimensions
    
    @param origin: The bottom left corner of rectange (if math_coords == True).
        The top left corner of rectangle (if math_coords == False)
    @type origin: (x,y) tuple
    @param dimensions: width and height of rectangle
    @type dimensions: (width, height) tuple
    @param fill: Whether to fill the rectangle or make it hollow
    @type fill: boolean
    @param color: Color to display at point
    @type color: int or string (0-F or 16 or '-' for transparent)
    """
    x, y = origin
    width, height = dimensions

    if fill:
        for x_offset in range(width):
            line((x + x_offset, y), (x + x_offset, y + height - 1), color)
    else:
        line((x, y), (x, y + height - 1), color)
        line((x, y + height - 1), (x + width - 1, y + height - 1), color)
        line((x + width - 1, y + height - 1), (x + width - 1, y), color)
        line((x + width - 1, y), (x, y), color)

def show(

)

Shows the current framebuffer on the display. Tells the led_driver to send framebuffer to SPI port.
Refreshes the display using current framebuffer.

@rtype: int @returns: 1 on success

def show():
    """Shows the current framebuffer on the display.
    Tells the led_driver to send framebuffer to SPI port.    
    Refreshes the display using current framebuffer.
    
    @rtype: int
    @returns: 1 on success
    """
    _init_check()
    return led_driver.flush()

def sprite(

sprite, origin=(0, 0), crop_origin=(0, 0), crop_dimensions=None)

Sets given sprite into the framebuffer.

@param origin: Bottom left position to diplay text (top left if math_coords == False) @type origin: (x,y) tuple @param crop_origin: Position to crop into the sprite relative to the origin @type crop_origin: (x,y) tuple @param crop_dimensions: x and y distance from the crop_origin to display - Keep at None to not crop and display sprite all the way to top right corner (bottom right for math_coords == False) @type crop_dimensions: (x,y) tuple

def sprite(sprite, origin=(0,0), crop_origin=(0,0), crop_dimensions=None):
    """Sets given sprite into the framebuffer.
    
    @param origin: Bottom left position to diplay text (top left if math_coords == False)
    @type origin: (x,y) tuple
    @param crop_origin: Position to crop into the sprite relative to the origin
    @type crop_origin: (x,y) tuple
    @param crop_dimensions: x and y distance from the crop_origin to display
        - Keep at None to not crop and display sprite all the way to top right corner (bottom right for math_coords == False)
    @type crop_dimensions: (x,y) tuple
    """
    if type(sprite) == str:
        sprite = LEDSprite(sprite)
    elif type(sprite) != LEDText and type(sprite) != LEDSprite:
        raise ValueError("Invalid sprite")
    
    _init_check()
    global width
    global height
    
    x_pos, y_pos = origin
    x_crop, y_crop = crop_origin
    
    # set up offset
    if x_crop < 0 or y_crop < 0:
        raise ValueError("crop_origin must be positive numbers")
    
    # set up crop
    if crop_dimensions is None:
        x_crop_dim, y_crop_dim = (sprite.width, sprite.height)   # no cropping
    else:
        x_crop_dim, y_crop_dim = crop_dimensions
        if x_crop_dim < 0 or y_crop_dim < 0:
            raise ValueError("crop_dimensions must be positive numbers")
        
    # set up start position
    x_start = x_pos + x_crop
    y_start = y_pos + y_crop 
    
    if x_start >= width or y_start >= height:
        return
        
    # set up end position
    x_end = min(x_pos + x_crop + x_crop_dim, width, x_pos + sprite.width)
    y_end = min(y_pos + y_crop + y_crop_dim, height, y_pos + sprite.height)
    
    # iterate through sprite and set points to led_driver
    y = max(y_start,0)
    while y < y_end:
        x = max(x_start, 0)
        while x < x_end:
            x_sprite = x - x_start + x_crop
            y_sprite = y - y_start + y_crop
            x_sprite = int(x_sprite)
            y_sprite = int(y_sprite)
            if container_math_coords:
                y_sprite = sprite.height - 1 - y_sprite
            point((x, y), color=sprite.bitmap[y_sprite][x_sprite])
            x += 1
        y += 1

def text(

text, origin=(0, 0), crop_origin=(0, 0), crop_dimensions=None, font_name='small', font_path=None)

Sets given string to be displayed on the led matrix

Example: >>> text("Hello World", (0,0), (0,1), (0,5)) >>> show() Displays only part of the first vertical line in 'H'

@param origin, crop_origin, crop_dimensions: See L{sprite}

@param text: text to display @param text: string or L{LEDText} @param font_name: Font folder (or .font) file to use as font face @type font_name: string @param font_path: Directory to use to look for fonts. If None it will used default directory in dist-packages @type font_path: string

@returns: LEDText sprite object used to create text @rtype: L{LEDText}

def text(text, origin=(0, 0), crop_origin=(0, 0), crop_dimensions=None, font_name="small", font_path=None):
    """Sets given string to be displayed on the led matrix
        
    Example:
        >>> text("Hello World", (0,0), (0,1), (0,5))
        >>> show()
        Displays only part of the first vertical line in 'H'
        
    @param origin, crop_origin, crop_dimensions: See L{sprite}
        
    @param text: text to display
    @param text: string or L{LEDText}
    @param font_name: Font folder (or .font) file to use as font face
    @type font_name: string
    @param font_path: Directory to use to look for fonts. If None it will used default directory in dist-packages
    @type font_path: string
    
    @returns: LEDText sprite object used to create text
    @rtype: L{LEDText}
    """
    if type(text) == str:
        text = LEDText(text, font_name=font_name, font_path=font_path)
    elif type(text) != LEDText and type(text) != LEDSprite:
        raise ValueError("Invalid text")
    sprite(text, origin, crop_origin, crop_dimensions)
    return text

Classes

class LEDSprite

Allows the creation of a LED Sprite that is defined in a text file. @note: The text file must only contain hex numbers 0-9, a-f, A-F, or - (dash) @note: The hex number indicates pixel color and "-" indicates a transparent pixel

class LEDSprite(object):
    """Allows the creation of a LED Sprite that is defined in a text file.
    @note: The text file must only contain hex numbers 0-9, a-f, A-F, or - (dash)
    @note: The hex number indicates pixel color and "-" indicates a transparent pixel
    """
    def __init__(self, filename=None, height=0, width=0, color=0x0):
        """Creates a L{LEDSprite} object from the given .spr file or image file or creates an empty sprite of given
        height and width if filename == None.
        
        @param filename: The full path location of a .spr sprite file or image file
        @type: string
        @param height: The height of given sprite if creating an empty sprite or want to resize a sprite from and image file.
        @type height: int
        @param width: The width of given sprite if creating an empty sprite or want to resize a sprite from and image file.
        @type width: int
        @param color: Color to display at point
        @type color: int or string (0-F or 16 or '-' for transparent)
        """
        bitmap = []
        bitmap_width = 0  # keep track of width and height
        bitmap_height = 0
        if filename is not None:
            filename = filename.strip()
            self.filename = filename    
            # get filetype
            proc = subprocess.Popen("file " + str(filename), shell=True, stdout=subprocess.PIPE)
            output, errors = proc.communicate()
            if type(output) == bytes:  # convert from byte to a string (happens if using python3)
                output = output.decode() 
            if errors is not None or (output.find("ERROR") != -1):
                raise IOError(output)
                
            if output.find("text") != -1 or output.find("FORTRAN") != -1:  # file is a text file
                if filename[-4:] != ".spr":
                    raise ValueError("Filename must have '.spr' extension.")
                f = open(filename, 'r')
                for line in f:
                    leds = [_convert_color(char) for char in line.split()]
                    # Determine if widths are consistent
                    if bitmap_width != 0:
                        if len(leds) != bitmap_width:
                            raise ValueError("Sprite has different widths")
                    else:
                        bitmap_width = len(leds)
                    bitmap.append(leds)
                    bitmap_height += 1
                f.close()
                
            elif output.find("image") != -1:  # file is an image file
                import scipy.misc, numpy, sys
                if sys.version_info[0] > 2:
                    raise ValueError("As of now, only python 2 supports images to sprites.")
                # if no height or width given try to fill as much of the display
                # with the image without stretching it
                if height <= 0 or width <= 0:
                    from PIL import Image
                    im = Image.open(filename)
                    im_width, im_height = im.size
                    bitmap_height = min(height, im_height)
                    bitmap_width = min(width, bitmap_height * (im_width / im_height))
                else:
                    bitmap_height = height
                    bitmap_width = width
                # pixelize and resize image with scipy
                image = scipy.misc.imread(filename, flatten=True)
                con_image = scipy.misc.imresize(image, (bitmap_width, bitmap_height), interp='cubic')
                con_image = numpy.transpose(con_image)  # convert from column-wise to row-wise
                con_image = numpy.fliplr(con_image)  # re-orient the image
                con_image = numpy.rot90(con_image, k=1)
                bitmap = [[int(pixel*16/255) for pixel in line] for line in con_image]  # convert to bitmap
            else:
                raise IOError("Unsupported filetype")
        else:
            # create an empty sprite of given height and width
            bitmap = [[color for i in range(width)] for j in range(height)]
            bitmap_height = height
            bitmap_width = width

        self.bitmap = bitmap
        # self.height = bitmap_height
        # self.width = bitmap_width

    @property
    def width(self):
        return len(self.bitmap[0])

    @property
    def height(self):
        return len(self.bitmap)

    def append(self, sprite):
        """Appends given sprite to the right of itself.
        
        @param sprite: sprite to append
        @type sprite: L{LEDSprite}
        @note: height of given sprite must be <= to itself otherwise will be truncated
        """
        for i, line in enumerate(self.bitmap):
            if i >= sprite.height:
                # fill in with transparent pixels
                tran_line = [0x10 for j in range(sprite.width)]
                self.bitmap[i] = sum([line, tran_line], [])
            else:
                self.bitmap[i] = sum([line, sprite.bitmap[i]], [])
        # update size
        # self.width += sprite.width

    def set_pixel(self, point, color=0xF):
        """Sets given color to given x and y coordinate in sprite

        @param point: point relative to sprite to set point
        @type point: (x,y)
        @param color: Color to display at point
        @type color: int or string (0-F or 16 or '-' for transparent)
        
        @return: None if coordinate is not valid
        """
        x, y = point
        if x >= self.width or y >= self.height or x < 0 or y < 0:
            return None
        self.bitmap[y][x] = _convert_color(color)

    def get_pixel(self, x, y):
        """
        @rtype: int
        @returns: int of color at given origin or None
        """
        if x >= self.width or y >= self.height or x < 0 or y < 0:
            return None
        return self.bitmap[y][x]

    
    def save_to_file(self, filename):
        """Saves sprite bitmap to given .spr file. 
        
        @param filename: relative filename path
        @type filename: string
        @note: It will truncate filename if it already exists.
        """
        filename = filename.strip()
        if filename[-4:] != ".spr":
            raise ValueError("Filename must have '.spr' extension.")
        f = open(filename, 'w')
        for line in self.bitmap:
            for pixel in line:
                if pixel > 15:
                    f.write("- ")
                else:
                    f.write(hex(pixel)[2] + " ")
            f.write("\n")
        f.close()
        
    def rotate(self, angle=90):
        """Rotates sprite at 90 degree intervals. 
        
        @returns: self
        @rtype: L{LEDSprite}
        
        @param angle: angle to rotate self in an interval of 90 degrees
        @type angle: int
        
        @returns: self
        @rtype: L{LEDSprite}
        @raises ValueError: If angle is not multiple of 90
        @note: If no angle given, will rotate sprite 90 degrees.
        """
        if angle % 90 != 0:
            raise ValueError("Angle must be a multiple of 90.")
            
        angle = angle % 360    
        if angle == 90:
            bitmap = []
            for i in range(self.width):
                bitmap.append([row[i] for row in reversed(self.bitmap)])
            self.bitmap = bitmap
            # swap height and width
            # temp = self.width
            # self.width = self.height
            # self.height = temp
            
        elif angle == 180:
            self.bitmap.reverse()
            for row in self.bitmap:
                row.reverse()
                
        elif angle == 270:
            bitmap = []
            for i in range(self.width-1,-1,-1):
                bitmap.append([row[i] for row in self.bitmap])
            self.bitmap = bitmap
            # swap height and width
            # temp = self.width
            # self.width = self.height
            # self.height = temp
        return self
        
    def rotated(self, angle=90):
        """Same as L{rotate} only it returns a copy of the rotated sprite
        and does not affect the original.
        @returns: Rotated sprite
        @rtype: L{LEDSprite}
        """
        sprite_copy = copy.deepcopy(self)
        sprite_copy.rotate(angle)
        return sprite_copy
        
    def copy(self):
        """Copies sprite
        @returns: A copy of sprite without affecting original sprite
        @rtype: L{LEDSprite}
        """
        return copy.deepcopy(self)
        
    def invert(self):
        """Inverts the sprite.
        @returns: self
        @rtype: L{LEDSprite}
        """
        for y, line in enumerate(self.bitmap):
            for x, pixel in enumerate(line):
                if pixel < 16:
                    self.bitmap[y][x] = 15 - pixel
                    
    def inverted(self):
        """Same as L{invert} only it returns a copy of the inverted sprite
        and does not affect the original.
        @returns: Inverted sprite
        @rtype: L{LEDSprite}
        """
        sprite_copy = copy.deepcopy(self)
        sprite_copy.invert()
        return sprite_copy
        
    def flip_horizontal(self):
        """Flips the sprite horizontally.
        @returns: self
        @rtype: L{LEDSprite}
        """
        self.bitmap.reverse()
        return self
        
    def flipped_horizontal(self):
        """Same as L{flip_horizontal} only it returns a copy of the flipped sprite
        and does not affect the original.
        @returns: sprite flipped horizontally
        @rtype: L{LEDSprite}
        """
        sprite_copy = copy.deepcopy(self)
        sprite_copy.flip_horizontal()
        return sprite_copy
        
        
    def flip_vertical(self):
        """Flips the sprite vertically.
        @returns: self
        @rtype: L{LEDSprite}
        """
        for line in self.bitmap:
            line.reverse()
        return self
        
    def flipped_vertical(self):
        """Same as L{flip_vertical} only it returns a copy of the flipped sprite
        and does not affect the original.
        @returns: sprite flipped vertically
        @rtype: L{LEDSprite}
        """
        sprite_copy = copy.deepcopy(self)
        sprite_copy.flip_vertical()
        return sprite_copy

Ancestors (in MRO)

Static methods

def __init__(

self, filename=None, height=0, width=0, color=0)

Creates a L{LEDSprite} object from the given .spr file or image file or creates an empty sprite of given height and width if filename == None.

@param filename: The full path location of a .spr sprite file or image file @type: string @param height: The height of given sprite if creating an empty sprite or want to resize a sprite from and image file. @type height: int @param width: The width of given sprite if creating an empty sprite or want to resize a sprite from and image file. @type width: int @param color: Color to display at point @type color: int or string (0-F or 16 or '-' for transparent)

def __init__(self, filename=None, height=0, width=0, color=0x0):
    """Creates a L{LEDSprite} object from the given .spr file or image file or creates an empty sprite of given
    height and width if filename == None.
    
    @param filename: The full path location of a .spr sprite file or image file
    @type: string
    @param height: The height of given sprite if creating an empty sprite or want to resize a sprite from and image file.
    @type height: int
    @param width: The width of given sprite if creating an empty sprite or want to resize a sprite from and image file.
    @type width: int
    @param color: Color to display at point
    @type color: int or string (0-F or 16 or '-' for transparent)
    """
    bitmap = []
    bitmap_width = 0  # keep track of width and height
    bitmap_height = 0
    if filename is not None:
        filename = filename.strip()
        self.filename = filename    
        # get filetype
        proc = subprocess.Popen("file " + str(filename), shell=True, stdout=subprocess.PIPE)
        output, errors = proc.communicate()
        if type(output) == bytes:  # convert from byte to a string (happens if using python3)
            output = output.decode() 
        if errors is not None or (output.find("ERROR") != -1):
            raise IOError(output)
            
        if output.find("text") != -1 or output.find("FORTRAN") != -1:  # file is a text file
            if filename[-4:] != ".spr":
                raise ValueError("Filename must have '.spr' extension.")
            f = open(filename, 'r')
            for line in f:
                leds = [_convert_color(char) for char in line.split()]
                # Determine if widths are consistent
                if bitmap_width != 0:
                    if len(leds) != bitmap_width:
                        raise ValueError("Sprite has different widths")
                else:
                    bitmap_width = len(leds)
                bitmap.append(leds)
                bitmap_height += 1
            f.close()
            
        elif output.find("image") != -1:  # file is an image file
            import scipy.misc, numpy, sys
            if sys.version_info[0] > 2:
                raise ValueError("As of now, only python 2 supports images to sprites.")
            # if no height or width given try to fill as much of the display
            # with the image without stretching it
            if height <= 0 or width <= 0:
                from PIL import Image
                im = Image.open(filename)
                im_width, im_height = im.size
                bitmap_height = min(height, im_height)
                bitmap_width = min(width, bitmap_height * (im_width / im_height))
            else:
                bitmap_height = height
                bitmap_width = width
            # pixelize and resize image with scipy
            image = scipy.misc.imread(filename, flatten=True)
            con_image = scipy.misc.imresize(image, (bitmap_width, bitmap_height), interp='cubic')
            con_image = numpy.transpose(con_image)  # convert from column-wise to row-wise
            con_image = numpy.fliplr(con_image)  # re-orient the image
            con_image = numpy.rot90(con_image, k=1)
            bitmap = [[int(pixel*16/255) for pixel in line] for line in con_image]  # convert to bitmap
        else:
            raise IOError("Unsupported filetype")
    else:
        # create an empty sprite of given height and width
        bitmap = [[color for i in range(width)] for j in range(height)]
        bitmap_height = height
        bitmap_width = width
    self.bitmap = bitmap

def append(

self, sprite)

Appends given sprite to the right of itself.

@param sprite: sprite to append @type sprite: L{LEDSprite} @note: height of given sprite must be <= to itself otherwise will be truncated

def append(self, sprite):
    """Appends given sprite to the right of itself.
    
    @param sprite: sprite to append
    @type sprite: L{LEDSprite}
    @note: height of given sprite must be <= to itself otherwise will be truncated
    """
    for i, line in enumerate(self.bitmap):
        if i >= sprite.height:
            # fill in with transparent pixels
            tran_line = [0x10 for j in range(sprite.width)]
            self.bitmap[i] = sum([line, tran_line], [])
        else:
            self.bitmap[i] = sum([line, sprite.bitmap[i]], [])

def copy(

self)

Copies sprite @returns: A copy of sprite without affecting original sprite @rtype: L{LEDSprite}

def copy(self):
    """Copies sprite
    @returns: A copy of sprite without affecting original sprite
    @rtype: L{LEDSprite}
    """
    return copy.deepcopy(self)

def flip_horizontal(

self)

Flips the sprite horizontally. @returns: self @rtype: L{LEDSprite}

def flip_horizontal(self):
    """Flips the sprite horizontally.
    @returns: self
    @rtype: L{LEDSprite}
    """
    self.bitmap.reverse()
    return self

def flip_vertical(

self)

Flips the sprite vertically. @returns: self @rtype: L{LEDSprite}

def flip_vertical(self):
    """Flips the sprite vertically.
    @returns: self
    @rtype: L{LEDSprite}
    """
    for line in self.bitmap:
        line.reverse()
    return self

def flipped_horizontal(

self)

Same as L{flip_horizontal} only it returns a copy of the flipped sprite and does not affect the original. @returns: sprite flipped horizontally @rtype: L{LEDSprite}

def flipped_horizontal(self):
    """Same as L{flip_horizontal} only it returns a copy of the flipped sprite
    and does not affect the original.
    @returns: sprite flipped horizontally
    @rtype: L{LEDSprite}
    """
    sprite_copy = copy.deepcopy(self)
    sprite_copy.flip_horizontal()
    return sprite_copy

def flipped_vertical(

self)

Same as L{flip_vertical} only it returns a copy of the flipped sprite and does not affect the original. @returns: sprite flipped vertically @rtype: L{LEDSprite}

def flipped_vertical(self):
    """Same as L{flip_vertical} only it returns a copy of the flipped sprite
    and does not affect the original.
    @returns: sprite flipped vertically
    @rtype: L{LEDSprite}
    """
    sprite_copy = copy.deepcopy(self)
    sprite_copy.flip_vertical()
    return sprite_copy

def get_pixel(

self, x, y)

@rtype: int @returns: int of color at given origin or None

def get_pixel(self, x, y):
    """
    @rtype: int
    @returns: int of color at given origin or None
    """
    if x >= self.width or y >= self.height or x < 0 or y < 0:
        return None
    return self.bitmap[y][x]

def invert(

self)

Inverts the sprite. @returns: self @rtype: L{LEDSprite}

def invert(self):
    """Inverts the sprite.
    @returns: self
    @rtype: L{LEDSprite}
    """
    for y, line in enumerate(self.bitmap):
        for x, pixel in enumerate(line):
            if pixel < 16:
                self.bitmap[y][x] = 15 - pixel

def inverted(

self)

Same as L{invert} only it returns a copy of the inverted sprite and does not affect the original. @returns: Inverted sprite @rtype: L{LEDSprite}

def inverted(self):
    """Same as L{invert} only it returns a copy of the inverted sprite
    and does not affect the original.
    @returns: Inverted sprite
    @rtype: L{LEDSprite}
    """
    sprite_copy = copy.deepcopy(self)
    sprite_copy.invert()
    return sprite_copy

def rotate(

self, angle=90)

Rotates sprite at 90 degree intervals.

@returns: self @rtype: L{LEDSprite}

@param angle: angle to rotate self in an interval of 90 degrees @type angle: int

@returns: self @rtype: L{LEDSprite} @raises ValueError: If angle is not multiple of 90 @note: If no angle given, will rotate sprite 90 degrees.

def rotate(self, angle=90):
    """Rotates sprite at 90 degree intervals. 
    
    @returns: self
    @rtype: L{LEDSprite}
    
    @param angle: angle to rotate self in an interval of 90 degrees
    @type angle: int
    
    @returns: self
    @rtype: L{LEDSprite}
    @raises ValueError: If angle is not multiple of 90
    @note: If no angle given, will rotate sprite 90 degrees.
    """
    if angle % 90 != 0:
        raise ValueError("Angle must be a multiple of 90.")
        
    angle = angle % 360    
    if angle == 90:
        bitmap = []
        for i in range(self.width):
            bitmap.append([row[i] for row in reversed(self.bitmap)])
        self.bitmap = bitmap
        # swap height and width
        # temp = self.width
        # self.width = self.height
        # self.height = temp
        
    elif angle == 180:
        self.bitmap.reverse()
        for row in self.bitmap:
            row.reverse()
            
    elif angle == 270:
        bitmap = []
        for i in range(self.width-1,-1,-1):
            bitmap.append([row[i] for row in self.bitmap])
        self.bitmap = bitmap
        # swap height and width
        # temp = self.width
        # self.width = self.height
        # self.height = temp
    return self

def rotated(

self, angle=90)

Same as L{rotate} only it returns a copy of the rotated sprite and does not affect the original. @returns: Rotated sprite @rtype: L{LEDSprite}

def rotated(self, angle=90):
    """Same as L{rotate} only it returns a copy of the rotated sprite
    and does not affect the original.
    @returns: Rotated sprite
    @rtype: L{LEDSprite}
    """
    sprite_copy = copy.deepcopy(self)
    sprite_copy.rotate(angle)
    return sprite_copy

def save_to_file(

self, filename)

Saves sprite bitmap to given .spr file.

@param filename: relative filename path @type filename: string @note: It will truncate filename if it already exists.

def save_to_file(self, filename):
    """Saves sprite bitmap to given .spr file. 
    
    @param filename: relative filename path
    @type filename: string
    @note: It will truncate filename if it already exists.
    """
    filename = filename.strip()
    if filename[-4:] != ".spr":
        raise ValueError("Filename must have '.spr' extension.")
    f = open(filename, 'w')
    for line in self.bitmap:
        for pixel in line:
            if pixel > 15:
                f.write("- ")
            else:
                f.write(hex(pixel)[2] + " ")
        f.write("\n")
    f.close()

def set_pixel(

self, point, color=15)

Sets given color to given x and y coordinate in sprite

@param point: point relative to sprite to set point @type point: (x,y) @param color: Color to display at point @type color: int or string (0-F or 16 or '-' for transparent)

@return: None if coordinate is not valid

def set_pixel(self, point, color=0xF):
    """Sets given color to given x and y coordinate in sprite
    @param point: point relative to sprite to set point
    @type point: (x,y)
    @param color: Color to display at point
    @type color: int or string (0-F or 16 or '-' for transparent)
    
    @return: None if coordinate is not valid
    """
    x, y = point
    if x >= self.width or y >= self.height or x < 0 or y < 0:
        return None
    self.bitmap[y][x] = _convert_color(color)

Instance variables

var bitmap

var height

var width

class LEDText

A L{LEDSprite} object of a piece of text.

class LEDText(LEDSprite):
    """A L{LEDSprite} object of a piece of text."""
    def __init__(self, message, char_spacing=1, font_name="small", font_path=None):
        """Creates a text sprite of the given string
        This object can be used the same way a sprite is useds
        
        @param char_spacing: number pixels between characters
        @type char_spacing: int
        @param font_name: Font folder (or .font) file to use as font face
        @type font_name: string
        @param font_path: Directory to use to look for fonts. If None it will used default directory in dist-packages
        @type font_path: string
        """
        if font_path is None: # if none, set up default font location
            this_dir, this_filename = os.path.split(__file__)
            # only use font_name if no custom font_path was given
            font_path = os.path.join(this_dir, "font")
            
        if not os.path.isdir(font_path):
            raise IOError("Font path does not exist.")
        orig_font_path = font_path

        # attach font_name to font_path
        font_path = os.path.join(font_path, font_name)
        
        # if font subdirectory doesn exist, attempt to open a .font file
        if not os.path.isdir(font_path):
            f = open(os.path.join(orig_font_path, font_name + ".font"), 'r')
            font_path = os.path.join(orig_font_path, f.read().strip())
            f.close()

        message = message.strip()
        if len(message) == 0:
            super(LEDSprite, self).__init__()
            return

        # start with first character as intial sprite object
        init_sprite = _char_to_sprite(message[0], font_path)
        # get general height and width of characters

        if len(message) > 1:
            # append other characters to initial sprite
            for char in message[1:]:
                # add character spacing
                init_sprite.append(LEDSprite(height=init_sprite.height, width=char_spacing, color=0x10))
                # now add next character
                sprite = _char_to_sprite(char, font_path)
                if sprite.height != init_sprite.height:
                    raise ValueError("Height of character sprites must all be the same.")
                # append
                init_sprite.append(sprite)

        self.bitmap = init_sprite.bitmap

Ancestors (in MRO)

Static methods

def __init__(

self, message, char_spacing=1, font_name='small', font_path=None)

Creates a text sprite of the given string This object can be used the same way a sprite is useds

@param char_spacing: number pixels between characters @type char_spacing: int @param font_name: Font folder (or .font) file to use as font face @type font_name: string @param font_path: Directory to use to look for fonts. If None it will used default directory in dist-packages @type font_path: string

def __init__(self, message, char_spacing=1, font_name="small", font_path=None):
    """Creates a text sprite of the given string
    This object can be used the same way a sprite is useds
    
    @param char_spacing: number pixels between characters
    @type char_spacing: int
    @param font_name: Font folder (or .font) file to use as font face
    @type font_name: string
    @param font_path: Directory to use to look for fonts. If None it will used default directory in dist-packages
    @type font_path: string
    """
    if font_path is None: # if none, set up default font location
        this_dir, this_filename = os.path.split(__file__)
        # only use font_name if no custom font_path was given
        font_path = os.path.join(this_dir, "font")
        
    if not os.path.isdir(font_path):
        raise IOError("Font path does not exist.")
    orig_font_path = font_path
    # attach font_name to font_path
    font_path = os.path.join(font_path, font_name)
    
    # if font subdirectory doesn exist, attempt to open a .font file
    if not os.path.isdir(font_path):
        f = open(os.path.join(orig_font_path, font_name + ".font"), 'r')
        font_path = os.path.join(orig_font_path, f.read().strip())
        f.close()
    message = message.strip()
    if len(message) == 0:
        super(LEDSprite, self).__init__()
        return
    # start with first character as intial sprite object
    init_sprite = _char_to_sprite(message[0], font_path)
    # get general height and width of characters
    if len(message) > 1:
        # append other characters to initial sprite
        for char in message[1:]:
            # add character spacing
            init_sprite.append(LEDSprite(height=init_sprite.height, width=char_spacing, color=0x10))
            # now add next character
            sprite = _char_to_sprite(char, font_path)
            if sprite.height != init_sprite.height:
                raise ValueError("Height of character sprites must all be the same.")
            # append
            init_sprite.append(sprite)
    self.bitmap = init_sprite.bitmap

def append(

self, sprite)

Appends given sprite to the right of itself.

@param sprite: sprite to append @type sprite: L{LEDSprite} @note: height of given sprite must be <= to itself otherwise will be truncated

def append(self, sprite):
    """Appends given sprite to the right of itself.
    
    @param sprite: sprite to append
    @type sprite: L{LEDSprite}
    @note: height of given sprite must be <= to itself otherwise will be truncated
    """
    for i, line in enumerate(self.bitmap):
        if i >= sprite.height:
            # fill in with transparent pixels
            tran_line = [0x10 for j in range(sprite.width)]
            self.bitmap[i] = sum([line, tran_line], [])
        else:
            self.bitmap[i] = sum([line, sprite.bitmap[i]], [])

def copy(

self)

Copies sprite @returns: A copy of sprite without affecting original sprite @rtype: L{LEDSprite}

def copy(self):
    """Copies sprite
    @returns: A copy of sprite without affecting original sprite
    @rtype: L{LEDSprite}
    """
    return copy.deepcopy(self)

def flip_horizontal(

self)

Flips the sprite horizontally. @returns: self @rtype: L{LEDSprite}

def flip_horizontal(self):
    """Flips the sprite horizontally.
    @returns: self
    @rtype: L{LEDSprite}
    """
    self.bitmap.reverse()
    return self

def flip_vertical(

self)

Flips the sprite vertically. @returns: self @rtype: L{LEDSprite}

def flip_vertical(self):
    """Flips the sprite vertically.
    @returns: self
    @rtype: L{LEDSprite}
    """
    for line in self.bitmap:
        line.reverse()
    return self

def flipped_horizontal(

self)

Same as L{flip_horizontal} only it returns a copy of the flipped sprite and does not affect the original. @returns: sprite flipped horizontally @rtype: L{LEDSprite}

def flipped_horizontal(self):
    """Same as L{flip_horizontal} only it returns a copy of the flipped sprite
    and does not affect the original.
    @returns: sprite flipped horizontally
    @rtype: L{LEDSprite}
    """
    sprite_copy = copy.deepcopy(self)
    sprite_copy.flip_horizontal()
    return sprite_copy

def flipped_vertical(

self)

Same as L{flip_vertical} only it returns a copy of the flipped sprite and does not affect the original. @returns: sprite flipped vertically @rtype: L{LEDSprite}

def flipped_vertical(self):
    """Same as L{flip_vertical} only it returns a copy of the flipped sprite
    and does not affect the original.
    @returns: sprite flipped vertically
    @rtype: L{LEDSprite}
    """
    sprite_copy = copy.deepcopy(self)
    sprite_copy.flip_vertical()
    return sprite_copy

def get_pixel(

self, x, y)

@rtype: int @returns: int of color at given origin or None

def get_pixel(self, x, y):
    """
    @rtype: int
    @returns: int of color at given origin or None
    """
    if x >= self.width or y >= self.height or x < 0 or y < 0:
        return None
    return self.bitmap[y][x]

def invert(

self)

Inverts the sprite. @returns: self @rtype: L{LEDSprite}

def invert(self):
    """Inverts the sprite.
    @returns: self
    @rtype: L{LEDSprite}
    """
    for y, line in enumerate(self.bitmap):
        for x, pixel in enumerate(line):
            if pixel < 16:
                self.bitmap[y][x] = 15 - pixel

def inverted(

self)

Same as L{invert} only it returns a copy of the inverted sprite and does not affect the original. @returns: Inverted sprite @rtype: L{LEDSprite}

def inverted(self):
    """Same as L{invert} only it returns a copy of the inverted sprite
    and does not affect the original.
    @returns: Inverted sprite
    @rtype: L{LEDSprite}
    """
    sprite_copy = copy.deepcopy(self)
    sprite_copy.invert()
    return sprite_copy

def rotate(

self, angle=90)

Rotates sprite at 90 degree intervals.

@returns: self @rtype: L{LEDSprite}

@param angle: angle to rotate self in an interval of 90 degrees @type angle: int

@returns: self @rtype: L{LEDSprite} @raises ValueError: If angle is not multiple of 90 @note: If no angle given, will rotate sprite 90 degrees.

def rotate(self, angle=90):
    """Rotates sprite at 90 degree intervals. 
    
    @returns: self
    @rtype: L{LEDSprite}
    
    @param angle: angle to rotate self in an interval of 90 degrees
    @type angle: int
    
    @returns: self
    @rtype: L{LEDSprite}
    @raises ValueError: If angle is not multiple of 90
    @note: If no angle given, will rotate sprite 90 degrees.
    """
    if angle % 90 != 0:
        raise ValueError("Angle must be a multiple of 90.")
        
    angle = angle % 360    
    if angle == 90:
        bitmap = []
        for i in range(self.width):
            bitmap.append([row[i] for row in reversed(self.bitmap)])
        self.bitmap = bitmap
        # swap height and width
        # temp = self.width
        # self.width = self.height
        # self.height = temp
        
    elif angle == 180:
        self.bitmap.reverse()
        for row in self.bitmap:
            row.reverse()
            
    elif angle == 270:
        bitmap = []
        for i in range(self.width-1,-1,-1):
            bitmap.append([row[i] for row in self.bitmap])
        self.bitmap = bitmap
        # swap height and width
        # temp = self.width
        # self.width = self.height
        # self.height = temp
    return self

def rotated(

self, angle=90)

Same as L{rotate} only it returns a copy of the rotated sprite and does not affect the original. @returns: Rotated sprite @rtype: L{LEDSprite}

def rotated(self, angle=90):
    """Same as L{rotate} only it returns a copy of the rotated sprite
    and does not affect the original.
    @returns: Rotated sprite
    @rtype: L{LEDSprite}
    """
    sprite_copy = copy.deepcopy(self)
    sprite_copy.rotate(angle)
    return sprite_copy

def save_to_file(

self, filename)

Saves sprite bitmap to given .spr file.

@param filename: relative filename path @type filename: string @note: It will truncate filename if it already exists.

def save_to_file(self, filename):
    """Saves sprite bitmap to given .spr file. 
    
    @param filename: relative filename path
    @type filename: string
    @note: It will truncate filename if it already exists.
    """
    filename = filename.strip()
    if filename[-4:] != ".spr":
        raise ValueError("Filename must have '.spr' extension.")
    f = open(filename, 'w')
    for line in self.bitmap:
        for pixel in line:
            if pixel > 15:
                f.write("- ")
            else:
                f.write(hex(pixel)[2] + " ")
        f.write("\n")
    f.close()

def set_pixel(

self, point, color=15)

Sets given color to given x and y coordinate in sprite

@param point: point relative to sprite to set point @type point: (x,y) @param color: Color to display at point @type color: int or string (0-F or 16 or '-' for transparent)

@return: None if coordinate is not valid

def set_pixel(self, point, color=0xF):
    """Sets given color to given x and y coordinate in sprite
    @param point: point relative to sprite to set point
    @type point: (x,y)
    @param color: Color to display at point
    @type color: int or string (0-F or 16 or '-' for transparent)
    
    @return: None if coordinate is not valid
    """
    x, y = point
    if x >= self.width or y >= self.height or x < 0 or y < 0:
        return None
    self.bitmap[y][x] = _convert_color(color)

Instance variables

var bitmap

var height

var width

Inheritance: LEDSprite.width

Sub-modules