Package rstem :: Package led_matrix
[hide private]
[frames] | no frames]

Source Code for Package rstem.led_matrix

  1  #!/usr/bin/env python3 
  2  # 
  3  # Copyright (c) 2014, Scott Silver Labs, LLC. 
  4  # 
  5  # Licensed under the Apache License, Version 2.0 (the "License"); 
  6  # you may not use this file except in compliance with the License. 
  7  # You may obtain a copy of the License at 
  8  # 
  9  #       http://www.apache.org/licenses/LICENSE-2.0 
 10  # 
 11  # Unless required by applicable law or agreed to in writing, software 
 12  # distributed under the License is distributed on an "AS IS" BASIS, 
 13  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 14  # See the License for the specific language governing permissions and 
 15  # limitations under the License. 
 16  # 
 17   
 18  import os 
 19  import re 
 20  import time 
 21  from . import led_driver     # c extension that controls led matrices and contains framebuffer 
 22  import copy 
 23  import subprocess 
 24   
 25  # global variables for use 
 26  BITS_PER_PIXEL = 4     # 4 bits to represent color 
 27  DIM_OF_MATRIX = 8     # 8x8 led matrix elements 
 28  initialized = False   # flag to indicate if LED has been initialized 
 29  spi_initialized = False  # flag to indicate if SPI bus as been initialized 
 30  width = 0    #: The width of the LED matrix grid 
 31  height = 0   #: The height of the LED matrix grid 
 32  container_math_coords = True 
33 34 35 -def _init_check():
36 """Checks that init_matrices has been called and throws an error if not 37 @raise RuntimeError: If matrices have not been initialized.""" 38 global initialized 39 if not initialized: 40 raise RuntimeError("Matrices must be initialized first.")
41
42 43 -def _valid_color(color):
44 """Checks if given color is number between 0-16 if an int or 0-f, or - if string 45 @param color: A color to check that is valid 46 @type color: string or int 47 """ 48 if type(color) == int: 49 if color < 0x0 or color > 0x10: 50 return False 51 return True 52 elif type(color) == str: 53 if not re.match(r'^[A-Za-z0-9-]$', color): 54 return False 55 return True 56 return False
57
58 59 -def _convert_color(color):
60 """Converts the given color to an int. 61 @param color: A color to be converted 62 @type color: string or int 63 64 @raise ValueError: Fails L{_valid_color} check 65 66 @return: Either the same int back again if color was an int or the int of a converted string 67 @rtype: int 68 """ 69 if not _valid_color(color): 70 raise ValueError("Invalid Color: must be a string between 0-f or '-'" + 71 " or a number 0-16 with 16 being transparent") 72 if type(color) == int: 73 return color 74 elif type(color) == str: 75 if color == '-': 76 return 0x10 77 return int(color, 16) 78 raise RuntimeError("Invalid color")
79
80 81 -def _convert_to_std_coords(x, y):
82 """Converts given math coordinates to standard programming coordinates 83 @param x: x coordinate in math coordinates 84 @param y: y coordinate in math coordinates 85 @rtype: tuple 86 @return: (x,y) coordinates in standard programming coordinates""" 87 return x, (height - 1 - y)
88
89 90 -def display_on_terminal():
91 """Toggles on and off the terminal display of the led matrix on show()""" 92 led_driver.display_on_terminal()
93
94 95 -def init_matrices(mat_list=[(0, 0, 0)], math_coords=True, spi_speed=125000, spi_port=0):
96 """Creates a chain of led matrices set at particular offsets into the frame buffer 97 The order of the led matrices in the list indicate the order they are 98 physically hooked up with the first one connected to Pi. 99 100 @param mat_list: list of tuples that contains led matrix and offset 101 (in math coordinates if math_coords==True, else in programmer coordinate if math_coords==False) 102 ex: [(0,0,0),(7,0,90)] 103 @type mat_list: list of size 2 or 3 tuples (mix or match) 104 @param math_coords: True to use math coordinates, False to use programming coordinates 105 @type math_coords: Boolean 106 """ 107 global initialized 108 global width 109 global height 110 global container_math_coords 111 if initialized: # free previous memory before attempting to do it again 112 cleanup() 113 114 width = max([matrix[0] for matrix in mat_list]) + DIM_OF_MATRIX 115 height = max([matrix[1] for matrix in mat_list]) + DIM_OF_MATRIX 116 container_math_coords = math_coords 117 if container_math_coords: 118 for i in range(len(mat_list)): 119 # convert y's to be standard programming coordinates 120 # and also move origin from bottom left to top left of matrix 121 if len(mat_list[i]) > 2: 122 mat_list[i] = (mat_list[i][0], (height-1 - mat_list[i][1]) - (DIM_OF_MATRIX-1), mat_list[i][2]) 123 else: 124 mat_list[i] = (mat_list[i][0], (height-1 - mat_list[i][1]) - (DIM_OF_MATRIX-1)) 125 led_driver.init_matrices(mat_list, len(mat_list), width, height) # flatten out tuple 126 127 # initialize spi bus 128 global spi_initialized 129 if not spi_initialized: 130 led_driver.init_SPI(spi_speed, spi_port) 131 spi_initialized = True 132 133 initialized = True
134
135 136 137 -def init_grid(num_rows=None, num_cols=None, angle=0, math_coords=True, spi_speed=125000, spi_port=0):
138 """Initiallizes led matrices in a grid pattern with either a given number 139 of rows and columns. 140 If num_rows and num_cols is not given, it will detect the number of matrices you have 141 and automatically set up a grid pattern with a max number of columns of 4. 142 143 @param num_rows: Number of rows the grid is in (when angle == 0) 144 @param num_cols: Number of columns the gird is in (when angle == 0) 145 @param math_coords: True to use math coordinates, False to use programming coordinates 146 @type math_coords: Boolean 147 @param angle: Angle to rotate the coordinate system once initialized. (must be 90 degree multiple) 148 @type angle: int 149 150 @raise ValueError: num_rows*num_cols != number of matrices 151 @raise ValueError: angle is not a multiple of 90 152 """ 153 154 155 # initialize spi bus right away because we need it for led_driver.detect() 156 global spi_initialized 157 if not spi_initialized: 158 led_driver.init_SPI(spi_speed, spi_port) 159 spi_initialized = True 160 161 162 if num_cols is None: 163 # num_rows, and num_cols are before rotation 164 # auto detect number of columns if none given 165 num_matrices = led_driver.detect() 166 if num_rows is None: 167 # if number of rows not given assume max of 4 columns per row 168 for cols in reversed(range(5)): # should never hit zero 169 rows = float(num_matrices)/cols 170 if rows.is_integer(): 171 num_rows = int(rows) 172 num_cols = cols 173 break 174 else: 175 num_cols = int(num_matrices/num_rows) 176 177 if num_cols*num_rows != num_matrices: # safety check 178 raise ValueError("Invalid number of rows and columns") 179 180 elif num_rows is None: 181 raise ValueError("If you are providing num_cols you must also provide num_rows.") 182 183 184 if angle % 90 != 0: 185 raise ValueError("Angle must be a multiple of 90.") 186 angle = angle % 360 187 188 mat_list = [] 189 if angle == 0: 190 for row in range(num_rows): # increment through rows downward 191 if row % 2 == 1: 192 for column in range(num_cols-1,-1,-1): # if odd increment right to left 193 mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 180)) # upside-down 194 else: 195 for column in range(num_cols): # if even, increment left to right 196 mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 0)) # right side up 197 elif angle == 90: # 90 degrees clockwise 198 for row in range(num_rows): 199 if row % 2 == 1: 200 for column in range(num_cols-1,-1,-1): # if odd, increment downward 201 mat_list.append(((num_rows-row - 1)*DIM_OF_MATRIX, column*DIM_OF_MATRIX, 270)) # 180 + 90 202 else: 203 for column in range(num_cols): # if even, increment upwards 204 mat_list.append(((num_rows-row - 1)*DIM_OF_MATRIX, column*DIM_OF_MATRIX, 90)) # 0 + 90 205 elif angle == 180: 206 for row in range(num_rows-1,-1,-1): # increment through rows upwards 207 if row % 2 == 0: 208 for column in range(num_cols-1,-1,-1): # if even increment right to left 209 mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 180)) # 0 + 180 210 else: 211 for column in range(num_cols): # if even increment left to right 212 mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 0)) # 180 + 180 213 elif angle == 270: # 90 degrees counter-clockwise 214 for row in range(num_rows): # increment columns right to left 215 if row % 2 == 1: 216 for column in range(num_cols-1,-1,-1): # if odd increment through rows upwards 217 mat_list.append((row*DIM_OF_MATRIX, (num_cols - 1- column)*DIM_OF_MATRIX, 90)) # 180 - 90 218 else: 219 for column in range(num_cols): # increment through rows downwards 220 mat_list.append((row*DIM_OF_MATRIX, (num_cols - 1 - column)*DIM_OF_MATRIX, 270)) # 0 - 90 = 270 221 222 global container_math_coords 223 init_matrices(mat_list, math_coords=False, spi_speed=spi_speed, spi_port=spi_port) 224 container_math_coords = math_coords
225
226 -def show():
227 """Shows the current framebuffer on the display. 228 Tells the led_driver to send framebuffer to SPI port. 229 Refreshes the display using current framebuffer. 230 231 @rtype: int 232 @returns: 1 on success 233 """ 234 _init_check() 235 return led_driver.flush()
236
237 -def cleanup():
238 """Unintializes matrices and frees all memory. 239 Should be called at the end of a program to avoid memory leaks. 240 Also, clears the display. 241 """ 242 global initialized 243 global spi_initialized 244 if initialized: 245 led_driver.fill(0x0) 246 led_driver.flush() 247 led_driver.shutdown_matrices() 248 initialized = False 249 spi_initialized = False
250
251 -def fill(color=0xF):
252 """Fills the framebuffer with the given color. 253 @param color: Color to fill the entire display with 254 @type color: int or string (0-F or 16 or '-' for transparent) 255 """ 256 _init_check() 257 led_driver.fill(_convert_color(color))
258
259 260 -def erase():
261 """Clears display""" 262 fill(0)
263
264 -def point(x, y=None, color=0xF):
265 """Sends point to the framebuffer. 266 @note: Will not display the point until show() is called. 267 268 @param x, y: Coordinates to place point 269 @type x, y: (int, int) 270 @param color: Color to display at point 271 @type color: int or string (0-F or 16 or '-' for transparent) 272 273 @rtype: int 274 @returns: 1 on success 275 """ 276 _init_check() 277 color = _convert_color(color) 278 if color < 16: # don't do anything if transparent 279 global width 280 global height 281 # If y is not given, then x is a tuple of the point 282 if y is None and type(x) is tuple: 283 x, y = x 284 if x < 0 or x >= width or y < 0 or y >= height: 285 return 286 if container_math_coords: 287 x, y = _convert_to_std_coords(x, y) 288 return led_driver.point(int(x), int(y), color)
289
290 -def rect(origin, dimensions, fill=True, color=0xF):
291 """Creates a rectangle from start point using given dimensions 292 293 @param origin: The bottom left corner of rectange (if math_coords == True). 294 The top left corner of rectangle (if math_coords == False) 295 @type origin: (x,y) tuple 296 @param dimensions: width and height of rectangle 297 @type dimensions: (width, height) tuple 298 @param fill: Whether to fill the rectangle or make it hollow 299 @type fill: boolean 300 @param color: Color to display at point 301 @type color: int or string (0-F or 16 or '-' for transparent) 302 """ 303 x, y = origin 304 width, height = dimensions 305 306 if fill: 307 for x_offset in range(width): 308 line((x + x_offset, y), (x + x_offset, y + height - 1), color) 309 else: 310 line((x, y), (x, y + height - 1), color) 311 line((x, y + height - 1), (x + width - 1, y + height - 1), color) 312 line((x + width - 1, y + height - 1), (x + width - 1, y), color) 313 line((x + width - 1, y), (x, y), color)
314
315 316 317 -def _sign(n):
318 return 1 if n >= 0 else -1
319
320 -def line(point_a, point_b, color=0xF):
321 """Create a line from point_a to point_b. 322 Uses Bresenham's Line Algorithm U{http://en.wikipedia.org/wiki/Bresenham's_line_algorithm} 323 @type point_a, point_b: (x,y) tuple 324 @param color: Color to display at point 325 @type color: int or string (0-F or 16 or '-' for transparent) 326 """ 327 x1, y1 = point_a 328 x2, y2 = point_b 329 dx = abs(x2 - x1) 330 dy = abs(y2 - y1) 331 sx = 1 if x1 < x2 else -1 332 sy = 1 if y1 < y2 else -1 333 err = dx - dy 334 while True: 335 point(x1, y1, color) 336 if (x1 == x2 and y1 == y2) or x1 >= width or y1 >= height: 337 break 338 e2 = 2*err 339 if e2 > -dy: 340 err -= dy 341 x1 += sx 342 if e2 < dx: 343 err += dx 344 y1 += sy
345
346 -def _line_fast(point_a, point_b, color=0xF):
347 """A faster c implementation of line. Use if you need the speed.""" 348 if container_math_coords: 349 point_a = _convert_to_std_coords(*point_a) 350 point_b = _convert_to_std_coords(*point_b) 351 led_driver.line(point_a[0], point_a[1], point_b[0], point_b[1], _convert_color(color))
352
353 354 -def text(text, origin=(0, 0), crop_origin=(0, 0), crop_dimensions=None, font_name="small", font_path=None):
355 """Sets given string to be displayed on the led matrix 356 357 Example: 358 >>> text("Hello World", (0,0), (0,1), (0,5)) 359 >>> show() 360 Displays only part of the first vertical line in 'H' 361 362 @param origin, crop_origin, crop_dimensions: See L{sprite} 363 364 @param text: text to display 365 @param text: string or L{LEDText} 366 @param font_name: Font folder (or .font) file to use as font face 367 @type font_name: string 368 @param font_path: Directory to use to look for fonts. If None it will used default directory in dist-packages 369 @type font_path: string 370 371 @returns: LEDText sprite object used to create text 372 @rtype: L{LEDText} 373 """ 374 if type(text) == str: 375 text = LEDText(text, font_name=font_name, font_path=font_path) 376 elif type(text) != LEDText and type(text) != LEDSprite: 377 raise ValueError("Invalid text") 378 sprite(text, origin, crop_origin, crop_dimensions) 379 return text
380
381 382 -def sprite(sprite, origin=(0,0), crop_origin=(0,0), crop_dimensions=None):
383 """Sets given sprite into the framebuffer. 384 385 @param origin: Bottom left position to diplay text (top left if math_coords == False) 386 @type origin: (x,y) tuple 387 @param crop_origin: Position to crop into the sprite relative to the origin 388 @type crop_origin: (x,y) tuple 389 @param crop_dimensions: x and y distance from the crop_origin to display 390 - Keep at None to not crop and display sprite all the way to top right corner (bottom right for math_coords == False) 391 @type crop_dimensions: (x,y) tuple 392 """ 393 if type(sprite) == str: 394 sprite = LEDSprite(sprite) 395 elif type(sprite) != LEDText and type(sprite) != LEDSprite: 396 raise ValueError("Invalid sprite") 397 398 _init_check() 399 global width 400 global height 401 402 x_pos, y_pos = origin 403 x_crop, y_crop = crop_origin 404 405 # set up offset 406 if x_crop < 0 or y_crop < 0: 407 raise ValueError("crop_origin must be positive numbers") 408 409 # set up crop 410 if crop_dimensions is None: 411 x_crop_dim, y_crop_dim = (sprite.width, sprite.height) # no cropping 412 else: 413 x_crop_dim, y_crop_dim = crop_dimensions 414 if x_crop_dim < 0 or y_crop_dim < 0: 415 raise ValueError("crop_dimensions must be positive numbers") 416 417 # set up start position 418 x_start = x_pos + x_crop 419 y_start = y_pos + y_crop 420 421 if x_start >= width or y_start >= height: 422 return 423 424 # set up end position 425 x_end = min(x_pos + x_crop + x_crop_dim, width, x_pos + sprite.width) 426 y_end = min(y_pos + y_crop + y_crop_dim, height, y_pos + sprite.height) 427 428 # iterate through sprite and set points to led_driver 429 y = max(y_start,0) 430 while y < y_end: 431 x = max(x_start, 0) 432 while x < x_end: 433 x_sprite = x - x_start + x_crop 434 y_sprite = y - y_start + y_crop 435 x_sprite = int(x_sprite) 436 y_sprite = int(y_sprite) 437 if container_math_coords: 438 y_sprite = sprite.height - 1 - y_sprite 439 point((x, y), color=sprite.bitmap[y_sprite][x_sprite]) 440 x += 1 441 y += 1
442
443 -def frame(numpy_bitmap):
444 """Sends the entire frame (represented as a numpy bitmap) to the led matrix. 445 446 @param numpy_bitmap: A ndarray representing the entire framebuffer to display. 447 @type numpy_bitmap: numpy ndarray 448 @note: bitmap dimensions must be the same as the dimensions of the container (non rotated). 449 """ 450 led_driver.frame(numpy_bitmap)
451
452 453 -class LEDSprite(object):
454 """Allows the creation of a LED Sprite that is defined in a text file. 455 @note: The text file must only contain hex numbers 0-9, a-f, A-F, or - (dash) 456 @note: The hex number indicates pixel color and "-" indicates a transparent pixel 457 """
458 - def __init__(self, filename=None, height=0, width=0, color=0x0):
459 """Creates a L{LEDSprite} object from the given .spr file or image file or creates an empty sprite of given 460 height and width if filename == None. 461 462 @param filename: The full path location of a .spr sprite file or image file 463 @type: string 464 @param height: The height of given sprite if creating an empty sprite or want to resize a sprite from and image file. 465 @type height: int 466 @param width: The width of given sprite if creating an empty sprite or want to resize a sprite from and image file. 467 @type width: int 468 @param color: Color to display at point 469 @type color: int or string (0-F or 16 or '-' for transparent) 470 """ 471 bitmap = [] 472 bitmap_width = 0 # keep track of width and height 473 bitmap_height = 0 474 if filename is not None: 475 filename = filename.strip() 476 self.filename = filename 477 # get filetype 478 proc = subprocess.Popen("file " + str(filename), shell=True, stdout=subprocess.PIPE) 479 output, errors = proc.communicate() 480 if type(output) == bytes: # convert from byte to a string (happens if using python3) 481 output = output.decode() 482 if errors is not None or (output.find("ERROR") != -1): 483 raise IOError(output) 484 485 if output.find("text") != -1 or output.find("FORTRAN") != -1: # file is a text file 486 if filename[-4:] != ".spr": 487 raise ValueError("Filename must have '.spr' extension.") 488 f = open(filename, 'r') 489 for line in f: 490 leds = [_convert_color(char) for char in line.split()] 491 # Determine if widths are consistent 492 if bitmap_width != 0: 493 if len(leds) != bitmap_width: 494 raise ValueError("Sprite has different widths") 495 else: 496 bitmap_width = len(leds) 497 bitmap.append(leds) 498 bitmap_height += 1 499 f.close() 500 501 elif output.find("image") != -1: # file is an image file 502 import scipy.misc, numpy, sys 503 if sys.version_info[0] > 2: 504 raise ValueError("As of now, only python 2 supports images to sprites.") 505 # if no height or width given try to fill as much of the display 506 # with the image without stretching it 507 if height <= 0 or width <= 0: 508 from PIL import Image 509 im = Image.open(filename) 510 im_width, im_height = im.size 511 bitmap_height = min(height, im_height) 512 bitmap_width = min(width, bitmap_height * (im_width / im_height)) 513 else: 514 bitmap_height = height 515 bitmap_width = width 516 # pixelize and resize image with scipy 517 image = scipy.misc.imread(filename, flatten=True) 518 con_image = scipy.misc.imresize(image, (bitmap_width, bitmap_height), interp='cubic') 519 con_image = numpy.transpose(con_image) # convert from column-wise to row-wise 520 con_image = numpy.fliplr(con_image) # re-orient the image 521 con_image = numpy.rot90(con_image, k=1) 522 bitmap = [[int(pixel*16/255) for pixel in line] for line in con_image] # convert to bitmap 523 else: 524 raise IOError("Unsupported filetype") 525 else: 526 # create an empty sprite of given height and width 527 bitmap = [[color for i in range(width)] for j in range(height)] 528 bitmap_height = height 529 bitmap_width = width 530 531 self.bitmap = bitmap
532 # self.height = bitmap_height 533 # self.width = bitmap_width 534 535 @property
536 - def width(self):
537 return len(self.bitmap[0])
538 539 @property
540 - def height(self):
541 return len(self.bitmap)
542
543 - def append(self, sprite):
544 """Appends given sprite to the right of itself. 545 546 @param sprite: sprite to append 547 @type sprite: L{LEDSprite} 548 @note: height of given sprite must be <= to itself otherwise will be truncated 549 """ 550 for i, line in enumerate(self.bitmap): 551 if i >= sprite.height: 552 # fill in with transparent pixels 553 tran_line = [0x10 for j in range(sprite.width)] 554 self.bitmap[i] = sum([line, tran_line], []) 555 else: 556 self.bitmap[i] = sum([line, sprite.bitmap[i]], [])
557 # update size 558 # self.width += sprite.width 559
560 - def set_pixel(self, point, color=0xF):
561 """Sets given color to given x and y coordinate in sprite 562 563 @param point: point relative to sprite to set point 564 @type point: (x,y) 565 @param color: Color to display at point 566 @type color: int or string (0-F or 16 or '-' for transparent) 567 568 @return: None if coordinate is not valid 569 """ 570 x, y = point 571 if x >= self.width or y >= self.height or x < 0 or y < 0: 572 return None 573 self.bitmap[y][x] = _convert_color(color)
574
575 - def get_pixel(self, x, y):
576 """ 577 @rtype: int 578 @returns: int of color at given origin or None 579 """ 580 if x >= self.width or y >= self.height or x < 0 or y < 0: 581 return None 582 return self.bitmap[y][x]
583 584
585 - def save_to_file(self, filename):
586 """Saves sprite bitmap to given .spr file. 587 588 @param filename: relative filename path 589 @type filename: string 590 @note: It will truncate filename if it already exists. 591 """ 592 filename = filename.strip() 593 if filename[-4:] != ".spr": 594 raise ValueError("Filename must have '.spr' extension.") 595 f = open(filename, 'w') 596 for line in self.bitmap: 597 for pixel in line: 598 if pixel > 15: 599 f.write("- ") 600 else: 601 f.write(hex(pixel)[2] + " ") 602 f.write("\n") 603 f.close()
604
605 - def rotate(self, angle=90):
606 """Rotates sprite at 90 degree intervals. 607 608 @returns: self 609 @rtype: L{LEDSprite} 610 611 @param angle: angle to rotate self in an interval of 90 degrees 612 @type angle: int 613 614 @returns: self 615 @rtype: L{LEDSprite} 616 @raises ValueError: If angle is not multiple of 90 617 @note: If no angle given, will rotate sprite 90 degrees. 618 """ 619 if angle % 90 != 0: 620 raise ValueError("Angle must be a multiple of 90.") 621 622 angle = angle % 360 623 if angle == 90: 624 bitmap = [] 625 for i in range(self.width): 626 bitmap.append([row[i] for row in reversed(self.bitmap)]) 627 self.bitmap = bitmap 628 # swap height and width 629 # temp = self.width 630 # self.width = self.height 631 # self.height = temp 632 633 elif angle == 180: 634 self.bitmap.reverse() 635 for row in self.bitmap: 636 row.reverse() 637 638 elif angle == 270: 639 bitmap = [] 640 for i in range(self.width-1,-1,-1): 641 bitmap.append([row[i] for row in self.bitmap]) 642 self.bitmap = bitmap 643 # swap height and width 644 # temp = self.width 645 # self.width = self.height 646 # self.height = temp 647 return self
648
649 - def rotated(self, angle=90):
650 """Same as L{rotate} only it returns a copy of the rotated sprite 651 and does not affect the original. 652 @returns: Rotated sprite 653 @rtype: L{LEDSprite} 654 """ 655 sprite_copy = copy.deepcopy(self) 656 sprite_copy.rotate(angle) 657 return sprite_copy
658
659 - def copy(self):
660 """Copies sprite 661 @returns: A copy of sprite without affecting original sprite 662 @rtype: L{LEDSprite} 663 """ 664 return copy.deepcopy(self)
665
666 - def invert(self):
667 """Inverts the sprite. 668 @returns: self 669 @rtype: L{LEDSprite} 670 """ 671 for y, line in enumerate(self.bitmap): 672 for x, pixel in enumerate(line): 673 if pixel < 16: 674 self.bitmap[y][x] = 15 - pixel
675
676 - def inverted(self):
677 """Same as L{invert} only it returns a copy of the inverted sprite 678 and does not affect the original. 679 @returns: Inverted sprite 680 @rtype: L{LEDSprite} 681 """ 682 sprite_copy = copy.deepcopy(self) 683 sprite_copy.invert() 684 return sprite_copy
685
686 - def flip_horizontal(self):
687 """Flips the sprite horizontally. 688 @returns: self 689 @rtype: L{LEDSprite} 690 """ 691 self.bitmap.reverse() 692 return self
693
694 - def flipped_horizontal(self):
695 """Same as L{flip_horizontal} only it returns a copy of the flipped sprite 696 and does not affect the original. 697 @returns: sprite flipped horizontally 698 @rtype: L{LEDSprite} 699 """ 700 sprite_copy = copy.deepcopy(self) 701 sprite_copy.flip_horizontal() 702 return sprite_copy
703 704
705 - def flip_vertical(self):
706 """Flips the sprite vertically. 707 @returns: self 708 @rtype: L{LEDSprite} 709 """ 710 for line in self.bitmap: 711 line.reverse() 712 return self
713
714 - def flipped_vertical(self):
715 """Same as L{flip_vertical} only it returns a copy of the flipped sprite 716 and does not affect the original. 717 @returns: sprite flipped vertically 718 @rtype: L{LEDSprite} 719 """ 720 sprite_copy = copy.deepcopy(self) 721 sprite_copy.flip_vertical() 722 return sprite_copy
723
724 725 -def _char_to_sprite(char, font_path):
726 """Converts given character to a sprite. 727 728 @param char: character to convert (must be of length == 1) 729 @type char: string 730 @param font_path: Relative location of font face to use. 731 @type font_path: string 732 @rtype: L{LEDSprite} 733 """ 734 if not (type(char) == str and len(char) == 1): 735 raise ValueError("Not a character") 736 orig_font_path = font_path 737 if char.isdigit(): 738 font_path = os.path.join(font_path, "numbers", char + ".spr") 739 elif char.isupper(): 740 font_path = os.path.join(font_path, "upper", char + ".spr") 741 elif char.islower(): 742 font_path = os.path.join(font_path, "lower", char + ".spr") 743 elif char.isspace(): 744 font_path = os.path.join(font_path, "space.spr") 745 else: 746 font_path = os.path.join(font_path, "misc", str(ord(char)) + ".spr") 747 748 if os.path.isfile(font_path): 749 return LEDSprite(font_path) 750 else: 751 return LEDSprite(os.path.join(orig_font_path, "unknown.spr"))
752
753 754 755 -class LEDText(LEDSprite):
756 """A L{LEDSprite} object of a piece of text."""
757 - def __init__(self, message, char_spacing=1, font_name="small", font_path=None):
758 """Creates a text sprite of the given string 759 This object can be used the same way a sprite is useds 760 761 @param char_spacing: number pixels between characters 762 @type char_spacing: int 763 @param font_name: Font folder (or .font) file to use as font face 764 @type font_name: string 765 @param font_path: Directory to use to look for fonts. If None it will used default directory in dist-packages 766 @type font_path: string 767 """ 768 if font_path is None: # if none, set up default font location 769 this_dir, this_filename = os.path.split(__file__) 770 # only use font_name if no custom font_path was given 771 font_path = os.path.join(this_dir, "font") 772 773 if not os.path.isdir(font_path): 774 raise IOError("Font path does not exist.") 775 orig_font_path = font_path 776 777 # attach font_name to font_path 778 font_path = os.path.join(font_path, font_name) 779 780 # if font subdirectory doesn exist, attempt to open a .font file 781 if not os.path.isdir(font_path): 782 f = open(os.path.join(orig_font_path, font_name + ".font"), 'r') 783 font_path = os.path.join(orig_font_path, f.read().strip()) 784 f.close() 785 786 message = message.strip() 787 if len(message) == 0: 788 super(LEDSprite, self).__init__() 789 return 790 791 # start with first character as intial sprite object 792 init_sprite = _char_to_sprite(message[0], font_path) 793 # get general height and width of characters 794 795 if len(message) > 1: 796 # append other characters to initial sprite 797 for char in message[1:]: 798 # add character spacing 799 init_sprite.append(LEDSprite(height=init_sprite.height, width=char_spacing, color=0x10)) 800 # now add next character 801 sprite = _char_to_sprite(char, font_path) 802 if sprite.height != init_sprite.height: 803 raise ValueError("Height of character sprites must all be the same.") 804 # append 805 init_sprite.append(sprite) 806 807 self.bitmap = init_sprite.bitmap
808
809 810 # run demo program if run by itself 811 -def _main():
812 init_matrices() 813 while 1: 814 for x in range(8): 815 for y in range(8): 816 point(x, y) 817 show() 818 time.sleep(0.5); 819 erase()
820 821 if __name__ == "__main__": 822 _main() 823