Source code for bibliopixel.layout.matrix_drawing

import math
from .. colors import COLORS, arithmetic
from .. util import log
from . import font

##########################################################################
# Drawing Functions
# Lovingly borrowed from Adafruit
# https://github.com/adafruit/Adafruit-GFX-Library/blob/master/Adafruit_GFX.cpp
##########################################################################


[docs]def draw_circle(setter, x0, y0, r, color=None): """ Draws a circle at point x0, y0 with radius r of the specified RGB color """ f = 1 - r ddF_x = 1 ddF_y = -2 * r x = 0 y = r setter(x0, y0 + r, color) setter(x0, y0 - r, color) setter(x0 + r, y0, color) setter(x0 - r, y0, color) while x < y: if f >= 0: y -= 1 ddF_y += 2 f += ddF_y x += 1 ddF_x += 2 f += ddF_x setter(x0 + x, y0 + y, color) setter(x0 - x, y0 + y, color) setter(x0 + x, y0 - y, color) setter(x0 - x, y0 - y, color) setter(x0 + y, y0 + x, color) setter(x0 - y, y0 + x, color) setter(x0 + y, y0 - x, color) setter(x0 - y, y0 - x, color)
def _draw_circle_helper(setter, x0, y0, r, cornername, color=None): f = 1 - r ddF_x = 1 ddF_y = -2 * r x = 0 y = r while x < y: if (f >= 0): y -= 1 ddF_y += 2 f += ddF_y x += 1 ddF_x += 2 f += ddF_x if (cornername & 0x4): setter(x0 + x, y0 + y, color) setter(x0 + y, y0 + x, color) if (cornername & 0x2): setter(x0 + x, y0 - y, color) setter(x0 + y, y0 - x, color) if (cornername & 0x8): setter(x0 - y, y0 + x, color) setter(x0 - x, y0 + y, color) if (cornername & 0x1): setter(x0 - y, y0 - x, color) setter(x0 - x, y0 - y, color) def _fill_circle_helper(setter, x0, y0, r, cornername, delta, color=None): f = 1 - r ddF_x = 1 ddF_y = -2 * r x = 0 y = r while (x < y): if (f >= 0): y -= 1 ddF_y += 2 f += ddF_y x += 1 ddF_x += 2 f += ddF_x if (cornername & 0x1): _draw_fast_vline(setter, x0 + x, y0 - y, 2 * y + 1 + delta, color) _draw_fast_vline(setter, x0 + y, y0 - x, 2 * x + 1 + delta, color) if (cornername & 0x2): _draw_fast_vline(setter, x0 - x, y0 - y, 2 * y + 1 + delta, color) _draw_fast_vline(setter, x0 - y, y0 - x, 2 * x + 1 + delta, color)
[docs]def fill_circle(setter, x0, y0, r, color=None): """Draws a filled circle at point x0,y0 with radius r and specified color""" _draw_fast_vline(setter, x0, y0 - r, 2 * r + 1, color) _fill_circle_helper(setter, x0, y0, r, 3, 0, color)
[docs]def draw_line(setter, x0, y0, x1, y1, color=None, colorFunc=None, aa=False): if aa: wu_line(setter, x0, y0, x1, y1, color, colorFunc) else: bresenham_line(setter, x0, y0, x1, y1, color, colorFunc)
# Bresenham's algorithm
[docs]def bresenham_line(setter, x0, y0, x1, y1, color=None, colorFunc=None): """Draw line from point x0,y0 to x,1,y1. Will draw beyond matrix bounds.""" steep = abs(y1 - y0) > abs(x1 - x0) if steep: x0, y0 = y0, x0 x1, y1 = y1, x1 if x0 > x1: x0, x1 = x1, x0 y0, y1 = y1, y0 dx = x1 - x0 dy = abs(y1 - y0) err = dx / 2 if y0 < y1: ystep = 1 else: ystep = -1 count = 0 for x in range(x0, x1 + 1): if colorFunc: color = colorFunc(count) count += 1 if steep: setter(y0, x, color) else: setter(x, y0, color) err -= dy if err < 0: y0 += ystep err += dx
# END Bresenham's algorithm # Xiaolin Wu's _line Algorithm
[docs]def wu_line(setter, x0, y0, x1, y1, color=None, colorFunc=None): funcCount = [0] # python2 hack since nonlocal not available def plot(x, y, level): c = color if colorFunc: c = colorFunc(funcCount[0]) funcCount[0] += 1 c = arithmetic.color_scale(color, int(255 * level)) setter(int(x), int(y), c) def ipart(x): return int(x) def fpart(x): return x - math.floor(x) def rfpart(x): return 1.0 - fpart(x) steep = abs(y1 - y0) > abs(x1 - x0) if steep: x0, y0 = y0, x0 x1, y1 = y1, x1 if x0 > x1: x0, x1 = x1, x0 y0, y1 = y1, y0 dx = x1 - x0 dy = y1 - y0 gradient = dy / dx # handle first endpoint xend = round(x0) yend = y0 + gradient * (xend - x0) xgap = rfpart(x0 + 0.5) xpxl1 = xend # this will be used in the main loop ypxl1 = ipart(yend) if steep: plot(ypxl1, xpxl1, rfpart(yend) * xgap) plot(ypxl1 + 1, xpxl1, fpart(yend) * xgap) else: plot(xpxl1, ypxl1, rfpart(yend) * xgap) plot(xpxl1, ypxl1 + 1, fpart(yend) * xgap) # first y-intersection for the main loop intery = yend + gradient # handle second endpoint xend = round(x1) yend = y1 + gradient * (xend - x1) xgap = fpart(x1 + 0.5) xpxl2 = xend # this will be used in the main loop ypxl2 = ipart(yend) if steep: plot(ypxl2, xpxl2, rfpart(yend) * xgap) plot(ypxl2 + 1, xpxl2, fpart(yend) * xgap) else: plot(xpxl2, ypxl2, rfpart(yend) * xgap) plot(xpxl2, ypxl2 + 1, fpart(yend) * xgap) # main loop for x in range(int(xpxl1 + 1), int(xpxl2)): if steep: plot(ipart(intery), x, rfpart(intery)) plot(ipart(intery) + 1, x, fpart(intery)) else: plot(x, ipart(intery), rfpart(intery)) plot(x, ipart(intery) + 1, fpart(intery)) intery = intery + gradient
# END Xiaolin Wu's _line Algorithm def _draw_fast_vline(setter, x, y, h, color=None, aa=False): draw_line(setter, x, y, x, y + h - 1, color, aa) def _draw_fast_hline(setter, x, y, w, color=None, aa=False): draw_line(setter, x, y, x + w - 1, y, color, aa)
[docs]def draw_rect(setter, x, y, w, h, color=None, aa=False): """Draw rectangle with top-left corner at x,y, width w and height h""" _draw_fast_hline(setter, x, y, w, color, aa) _draw_fast_hline(setter, x, y + h - 1, w, color, aa) _draw_fast_vline(setter, x, y, h, color, aa) _draw_fast_vline(setter, x + w - 1, y, h, color, aa)
[docs]def fill_rect(setter, x, y, w, h, color=None, aa=False): """Draw solid rectangle with top-left corner at x,y, width w and height h""" for i in range(x, x + w): _draw_fast_vline(setter, i, y, h, color, aa)
[docs]def draw_round_rect(setter, x, y, w, h, r, color=None, aa=False): """Draw rectangle with top-left corner at x,y, width w, height h, and corner radius r. """ _draw_fast_hline(setter, x + r, y, w - 2 * r, color, aa) # Top _draw_fast_hline(setter, x + r, y + h - 1, w - 2 * r, color, aa) # Bottom _draw_fast_vline(setter, x, y + r, h - 2 * r, color, aa) # Left _draw_fast_vline(setter, x + w - 1, y + r, h - 2 * r, color, aa) # Right # draw four corners _draw_circle_helper(setter, x + r, y + r, r, 1, color, aa) _draw_circle_helper(setter, x + w - r - 1, y + r, r, 2, color, aa) _draw_circle_helper(setter, x + w - r - 1, y + h - r - 1, r, 4, color, aa) _draw_circle_helper(setter, x + r, y + h - r - 1, r, 8, color, aa)
[docs]def fill_round_rect(setter, x, y, w, h, r, color=None, aa=False): """Draw solid rectangle with top-left corner at x,y, width w, height h, and corner radius r""" fill_rect(setter, x + r, y, w - 2 * r, h, color, aa) _fill_circle_helper(setter, x + w - r - 1, y + r, r, 1, h - 2 * r - 1, color, aa) _fill_circle_helper(setter, x + r, y + r, r, 2, h - 2 * r - 1, color, aa)
[docs]def draw_triangle(setter, x0, y0, x1, y1, x2, y2, color=None, aa=False): """Draw triangle with points x0,y0 - x1,y1 - x2,y2""" draw_line(setter, x0, y0, x1, y1, color, aa) draw_line(setter, x1, y1, x2, y2, color, aa) draw_line(setter, x2, y2, x0, y0, color, aa)
[docs]def fill_triangle(setter, x0, y0, x1, y1, x2, y2, color=None, aa=False): """Draw solid triangle with points x0,y0 - x1,y1 - x2,y2""" a = b = y = last = 0 if y0 > y1: y0, y1 = y1, y0 x0, x1 = x1, x0 if y1 > y2: y2, y1 = y1, y2 x2, x1 = x1, x2 if y0 > y1: y0, y1 = y1, y0 x0, x1 = x1, x0 if y0 == y2: # Handle awkward all-on-same-line case as its own thing a = b = x0 if x1 < a: a = x1 elif x1 > b: b = x1 if x2 < a: a = x2 elif x2 > b: b = x2 _draw_fast_hline(setter, a, y0, b - a + 1, color, aa) dx01 = x1 - x0 dy01 = y1 - y0 dx02 = x2 - x0 dy02 = y2 - y0 dx12 = x2 - x1 dy12 = y2 - y1 sa = 0 sb = 0 # For upper part of triangle, find scanline crossings for segments # 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 # is included here (and second loop will be skipped, avoiding a /0 # error there), otherwise scanline y1 is skipped here and handled # in the second loop...which also avoids a /0 error here if y0=y1 # (flat-topped triangle). if y1 == y2: last = y1 # include y1 scanline else: last = y1 - 1 # skip it for y in range(y, last + 1): a = x0 + sa / dy01 b = x0 + sb / dy02 sa += dx01 sb += dx02 if a > b: a, b = b, a _draw_fast_hline(setter, a, y, b - a + 1, color, aa) # For lower part of triangle, find scanline crossings for segments # 0-2 and 1-2. This loop is skipped if y1=y2. sa = dx12 * (y - y1) sb = dx02 * (y - y0) for y in range(y, y2 + 1): a = x1 + sa / dy12 b = x0 + sb / dy02 sa += dx12 sb += dx02 if a > b: a, b = b, a _draw_fast_hline(setter, a, y, b - a + 1, color, aa)
[docs]def draw_char(fonts, setter, width, height, x, y, c, color, bg, aa=False, font=font.default_font, font_scale=1): if font_scale < 1: log.error('font_scale %s must be >= 1', font_scale) font_scale = 1 f = fonts[font] fh = f['height'] FONT = f['data'] c = ord(c) # make it the int value if c < f['bounds'][0] or c > f['bounds'][1]: c_data = f['undef'] else: c_data = FONT[c - f['bounds'][0]] fw = len(c_data) for i in range(fw + f['sep']): x_pos = x + (i * font_scale) if max(0, 1 - font_scale * fw) <= x_pos < width: if i >= fw: line = 0 else: line = FONT[c][i] for j in range(fh): y_pos = y + (j * font_scale) if ((y_pos < height) and (y_pos + fh * font_scale - 1) >= 0): if line & 0x1: if font_scale == 1: setter(x_pos, y_pos, color) else: fill_rect(setter, x_pos, y_pos, font_scale, font_scale, color, aa) elif bg != color and bg is not None: if font_scale == 1: setter(x_pos, y_pos, bg) else: fill_rect(setter, x_pos, y_pos, font_scale, font_scale, bg, aa) line >>= 1 return fw + f['sep']
[docs]def draw_text(fonts, setter, text, width, height, x=0, y=0, color=None, bg=COLORS.Off, aa=False, font=font.default_font, font_scale=1): fh = fonts[font]['height'] for c in text: if c == '\n': y += font_scale * fh x = 0 elif c == '\r': pass # skip it else: fw = draw_char(fonts, setter, width, height, x, y, c, color, bg, aa, font, font_scale) x += font_scale * fw if x >= width: break