Skip to content

Commit

Permalink
Add thick line support (#16)
Browse files Browse the repository at this point in the history
Adds single thick lines and polylines. Joins are round, ends of single
lines can be butt or round.
  • Loading branch information
corranwebster authored Oct 21, 2024
1 parent 9035351 commit 58ec39e
Show file tree
Hide file tree
Showing 6 changed files with 651 additions and 0 deletions.
106 changes: 106 additions & 0 deletions examples/lines_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import asyncio
from array import array
import framebuf
import random
import math

from tempe.bitmaps import Bitmaps, ColoredBitmaps
from tempe.colormaps.viridis import viridis
from tempe.colormaps.viridis import viridis
from tempe.data_view import Repeat, Range, Interpolated
from tempe.geometry import RowGeometry, ColumnGeometry
from tempe.surface import Surface
from tempe.shapes import Rectangles
from tempe.lines import WideLines, WidePolyLines
from tempe.display import FileDisplay

random.seed(0)

surface = Surface()

# a buffer one quarter the size of the screen
working_buffer = array('H', bytearray(2*320*61))

# fill the background with white pixels
background = Rectangles([(0, 0, 320, 240)], [0xffff])
surface.add_shape('BACKGROUND', background)


# draw some lines
lines = WideLines(
ColumnGeometry([
Range(8, 96, 8), Repeat(20), Range(8, 184, 16), Repeat(100),
Range(1, 11)
]),
Interpolated(viridis, 10),
clip=(0, 0, 160, 120)
)
surface.add_shape('DRAWING', lines)


# draw some lines in the opposite direction
lines = WideLines(
ColumnGeometry([
Range(8, 96, 8) + 160, Repeat(100), Range(8, 184, 16) + 160, Repeat(20),
Range(1, 11)
]),
Interpolated(viridis, 10),
clip=(160, 0, 160, 120),
round=False,
)
surface.add_shape('DRAWING', lines)

# draw some polylines
polylines = WidePolyLines(
RowGeometry([
(
array('h', [
10 + 30 * i, 125,
10 + 30 * i, 225,
15 + 30 * i, 225,
25 + 30 * i, 165,
10 + 30 * i, 125,
]),
i+1,
)
for i in range(10)
]),
Interpolated(viridis, 10),
clip=(0, 120, 320, 120)
)
surface.add_shape('DRAWING', polylines)

def main(surface, working_buffer):


async def init_display():
from devices.st7789 import ST7789
from machine import Pin, SPI

spi = SPI(0, baudrate=62_500_000, phase=1, polarity=1, sck=Pin(18, Pin.OUT), mosi=Pin(19, Pin.OUT), miso=Pin(16, Pin.OUT))
backlight = Pin(20, Pin.OUT)
display = ST7789(spi, cs_pin=Pin(17, Pin.OUT, value=1), dc_pin=Pin(16, Pin.OUT))
backlight(1)
await display.init()
return display

# set up the display object
display = asyncio.run(init_display())

# refresh the display
display.clear()
surface.refresh(display, working_buffer)

if __name__ == '__main__':

# if we have an actual screen, use it
main(surface, working_buffer)

elif __name__ != '__test__':

# set up the display object
display = FileDisplay('lines.rgb565', (320, 240))
# refresh the display
with display:
display.clear()
surface.refresh(display, working_buffer)
34 changes: 34 additions & 0 deletions src/tempe/_speedups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import micropython


@micropython.viper
def line_points(x0: int, y0: int, x1: int, y1: int, w: int, d: int, vertices: ptr16):
dx: int = x1 - x0
dy: int = y1 - y0

# stuff to handle inter division always round down, when we really
# want away from 0
if dx == 0:
mx = -((w + 1) // 2)
else:
if dy > 0:
mx = -w * dy // d
else:
mx = -(w * dy // d)
if dy == 0:
my = (w + 1) // 2
else:
if dx > 0:
my = -(-w * dx // d)
else:
my = w * dx // d

vertices[0] = x0 + mx
vertices[1] = y0 + my
vertices[2] = x1 + mx
vertices[3] = y1 + my
vertices[4] = x1 - mx
vertices[5] = y1 - my
vertices[6] = x0 - mx
vertices[7] = y0 - my

130 changes: 130 additions & 0 deletions src/tempe/lines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from array import array
from math import sqrt
import micropython

from .shapes import ColoredGeometry


class WideLines(ColoredGeometry):
"""Render multiple colored line segments with variable width.
Geometry should produce x0, y0, x1, y1, width arrays.
"""

def __init__(self, geometry, colors, *, round=True, surface=None, clip=None):
super().__init__(geometry, colors, surface=surface, clip=clip)
self.round = round

def draw(self, buffer, x=0, y=0):
vertices = array('h', bytearray(16))
should_round = self.round
for geometry, color in self:
x0 = geometry[0]
y0 = geometry[1]
x1 = geometry[2]
y1 = geometry[3]
w = geometry[4]
if w < 2:
buffer.line(x0 - x, y0 - y, x1 - x, y1 - y, color)
else:
d = 2 * int(sqrt((x1 - x0)**2 + (y1 - y0)**2))
line_points(x0, y0, x1, y1, w, d, vertices)
buffer.poly(-x, -y, vertices, color, True)
if should_round:
r = w // 2
buffer.ellipse(x0 - x, y0 - y, r, r, color, True)
buffer.ellipse(x1 - x, y1 - y, r, r, color, True)

def _bounds(self):
max_x = -0x7fff
min_x = 0x7fff
max_y = -0x7fff
min_y = 0x7fff
for geometry in self.geometry:
max_x = max(max_x, geometry[0] + geometry[4], geometry[2] + geometry[4])
min_x = min(min_x, geometry[0] - geometry[4], geometry[2] - geometry[4])
max_y = max(max_y, geometry[1] + geometry[4], geometry[3] + geometry[4])
min_y = min(min_y, geometry[1] - geometry[4], geometry[3] - geometry[4])

return (min_x, min_y, max_x - min_x, max_y - min_y)



class WidePolyLines(ColoredGeometry):
"""Render multiple colored polylines with variable width.
Geometry should produce array of [x0, y0, x1, y1, ...] and width.
"""

def draw(self, buffer, x=0, y=0):
vertices = array('h', bytearray(16))
for geometry, color in self:
lines, w = geometry
for i in range(0, len(lines) - 2, 2):
x0 = lines[i]
y0 = lines[i+1]
x1 = lines[i+2]
y1 = lines[i+3]
if w < 2:
buffer.line(x0 - x, y0 - y, x1 - x, y1 - y, color)
else:
d = 2 * int(sqrt((x1 - x0)**2 + (y1 - y0)**2))
line_points(x0, y0, x1, y1, w, d, vertices)
buffer.poly(-x, -y, vertices, color, True)
if w >= 2:
r = w // 2
for i in range(0, len(lines), 2):
x0 = lines[i]
y0 = lines[i+1]
buffer.ellipse(x0 - x, y0 - y, r, r, color, True)

def _bounds(self):
max_x = -0x7fff
min_x = 0x7fff
max_y = -0x7fff
min_y = 0x7fff
for geometry in self.geometry:
lines, w = geometry
for i in range(0, len(lines), 2):
max_x = max(max_x, lines[i] + w)
min_x = min(min_x, lines[i] - w)
max_y = max(max_y, lines[i+1] + w)
min_y = min(min_y, lines[i+1] - w)

return (min_x, min_y, max_x - min_x, max_y - min_y)


def line_points(x0, y0, x1, y1, w, d, vertices):
dx = x1 - x0
dy = y1 - y0

# stuff to handle inter division always round down, when we really
# want away from 0
if dx == 0:
mx = -((w + 1) // 2)
else:
if dy > 0:
mx = -w * dy // d
else:
mx = -(w * dy // d)
if dy == 0:
my = (w + 1) // 2
else:
if dx > 0:
my = -(-w * dx // d)
else:
my = w * dx // d

vertices[0] = x0 + mx
vertices[1] = y0 + my
vertices[2] = x1 + mx
vertices[3] = y1 + my
vertices[4] = x1 - mx
vertices[5] = y1 - my
vertices[6] = x0 - mx
vertices[7] = y0 - my

try:
from ._speedups import line_points
except SyntaxError:
pass
13 changes: 13 additions & 0 deletions src/tempe/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
{
"urls": [
["tempe/__init__.py", "github:unital/tempe/src/tempe/__init__.py"],
["tempe/_speedups.py", "github:unital/tempe/src/tempe/_speedups.py"],
["tempe/_data_view_math.py", "github:unital/tempe/src/tempe/_data_view_math.py"],
["tempe/bitmaps.py", "github:unital/tempe/src/tempe/bitmaps.py"],
["tempe/data_view.py", "github:unital/tempe/src/tempe/data_view.py"],
["tempe/display.py", "github:unital/tempe/src/tempe/display.py"],
["tempe/geometry.py", "github:unital/tempe/src/tempe/geometry.py"],
["tempe/lines.py", "github:unital/tempe/src/tempe/lines.py"],
["tempe/markers.py", "github:unital/tempe/src/tempe/markers.py"],
["tempe/raster.py", "github:unital/tempe/src/tempe/raster.py"],
["tempe/shapes.py", "github:unital/tempe/src/tempe/shapes.py"],
["tempe/surface.py", "github:unital/tempe/src/tempe/surface.py"],
["tempe/text.py", "github:unital/tempe/src/tempe/text.py"],
["tempe/util.py", "github:unital/tempe/src/tempe/util.py"]
],
"version": "0.1"
}
Loading

0 comments on commit 58ec39e

Please sign in to comment.