Skip to content

Instantly share code, notes, and snippets.

@PennRobotics
Last active August 2, 2022 15:43
Show Gist options
  • Save PennRobotics/534a5b3338cc82c8c03bfa8795253779 to your computer and use it in GitHub Desktop.
Save PennRobotics/534a5b3338cc82c8c03bfa8795253779 to your computer and use it in GitHub Desktop.
Remake of baoilleach/bresenham.py for Python 3
DEBUG = print if 0 else lambda *_:()
from math import cos, sin, pi
ASPECT = 15./10.
def bresenham_line(start, end):
symbols = getsymbols(start, end)
(x1, y1), (x2, y2) = start, end
steep = False
coords = []
dx = abs(x2 - x1)
dy = abs(y2 - y1)
sx = 1 if x2 > x1 else -1
sy = 1 if y2 > y1 else -1
if dy > dx:
steep = True
x1,y1 = y1,x1
dx,dy = dy,dx
sx,sy = sy,sx
d = (2 * dy) - dx
syms = []
delta = 0
for i in range(dx):
syms.append(symbols[d>=0])
delta = 0
if syms[-1] in "/\\":
delta = getdelta(start,end)
if steep:
coords.append((y1,x1 + delta))
else:
coords.append((x1,y1 + delta))
while d >= 0:
y1 = y1 + sy
d = d - (2 * dx)
x1 = x1 + sx
d = d + (2 * dy)
syms.append(symbols[d>=0])
delta = 0
if syms[-1] in "/\\":
delta = getdelta(start,end)
coords.append((x2,y2 + delta))
if len(coords) > 1:
if syms[0] == "|":
if coords[0][1] < coords[1][1]:
syms, coords = syms[1:], coords[1:]
if syms[-1] == "|":
DEBUG("HAPPENED!")
if coords[-1][1] < coords[-2][1]:
syms, coords = syms[:-1], coords[:-1]
return coords, syms
class Screen(object):
def __init__(self, width, height):
self.w = width
self.h = height
self.buf = [[" "]*self.w for x in range(self.h)]
def __str__(self):
ans=["-----START"]
for x in self.buf:
ans.append("".join(x))
ans.append("-----END")
return "\n".join(ans)
def line(self, start, end):
coords, syms = bresenham_line( start, end )
DEBUG(coords, syms)
for (a, b), sym in zip(coords, syms):
if 0<=b<self.h and 0<=a<self.w:
self.buf[b][a] = sym
def text(self, text, pos):
start = pos[0]
for i, x in enumerate(text):
if 0 <= pos[1] < self.h and 0 <= start+i < self.w:
self.buf[pos[1]][start + i] = x
def getsymbols(start ,end):
"""
>>> getsymbols((9, 4), (19, 5))
['_', '\\\\']
>>> getsymbols((9, 4), (0, 3))
['_', '\\\\']
>>> getsymbols((9, 4), (15, 8))
['_', '\\\\']
>>> getsymbols((9, 4), (7, 9))
['|', '/']
>>> getsymbols((9, 4), (3, 0))
['_', '\\\\']
>>> getsymbols((9, 4), (1, 7))
['_', '/']
>>> getsymbols((9, 4), (12, 9))
['|', '\\\\']
"""
if end[0] < start[0]:
start, end = end, start
if end[0] == start[0]:
slope = 1e99
else:
slope = (end[1] - start[1]) / float((end[0] - start[0]))
if slope > 0:
if slope > 1.0:
ans = ["|", '\\']
else:
ans = ["_", '\\']
else:
if slope > -1.0:
ans = ["_", "/"]
else:
ans = ["|", "/"]
return ans
def getdelta(start, end):
"""
>>> getdelta((9, 4), (18, 7))
1
>>> getdelta((9, 4), (12, 9))
0
>>> getdelta((9, 4), (7, 9))
0
"""
return end[1] > start[1] and abs((end[1] - start[1]) / (end[0] - start[0])) <= 1.0
def mytest():
screen = Screen(10,10)
screen.buf[0] = list("0123456789")
for i in range(10):
screen.buf[i][0] = str(i)
screen.line( (2,3), (3,5))
print(screen)
DEBUG(getsymbols((25,14), (17,11)))
if __name__ == "__main__":
mytest()
star = True
square = True
normal = True
if star:
w, h = 170 ,48
w, h = 78, 48
screen = Screen(w, h)
start = 0
x, y = round(w/2.)-1, round(h/2.)-1
oldx, oldy = w-1, y
for ang in range(start, 360 + start + 10, 10):
x2 = round(x + w/2*cos(ang*pi/180))
y2 = round(y + h/2*sin(ang*pi/180))
screen.line((x, y), (x2, y2))
screen.line((oldx, oldy), (x2, y2))
oldx,oldy=x2,y2
print(screen)
if square:
w = 30
h = round(w/ASPECT)
screen = Screen(w, h)
points = [(0, 0), (w-1, 0), (w-1, h-1), (0, h-1), (0, 0)]
for src, dest in zip(points, points[1:]):
screen.line(src, dest)
start = 30
x, y = round(w/2.)-1, round(h/2.)-1
for i, size in enumerate([w/2., w/2. - 4]):
x2, y2 = round(x+size*cos(start*pi/180.)),round(y+(size/ASPECT)*sin(start*pi/180.))
oldx, oldy = x2, y2
for ang in range(start, 360 + start + 60, 60):
x2, y2 = round(x+size*cos(ang*pi/180.)),round(y+(size/ASPECT)*sin(ang*pi/180.))
if i==0 or (i==1 and (ang/60)%2==0 ):
screen.line((oldx,oldy),(x2,y2))
oldx,oldy=x2,y2
print(screen)
if normal:
output = open('test.bbl','r').read()
DEBUG(output)
w, h = 76, 30
scale = 0.3
screen = Screen(w, h)
for line in output.split("\n"):
DEBUG(line)
fields = line.split()
if line.startswith("NewCanvas"):
vals = [float(x) for x in fields[1:3]]
cw, ch = vals[0], vals[1]/ASPECT
sx, sy = w/cw, h/ch
scale = min(sx, sy)
elif line.startswith("DrawLine"):
tmp = [fields[i] for i in [1,2,4,5]]
vals = [float(x)*scale for x in tmp]
screen.line((round(vals[0]), round(vals[1]/ASPECT)),
(round(vals[2]), round(vals[3]/ASPECT)))
elif line.startswith("DrawText"):
vals = [float(x)*scale for x in fields[1:3]]
screen.text(fields[3][1:-1],
(round(vals[0]), round(vals[1]/ASPECT)))
print(screen)
NewCanvas 149.3 140.0
SetPenColor 0.0 0.0 0.0 1.0 (rgba)
DrawLine 109.3 100.0 to 74.6 80.0
SetPenColor 0.0 0.0 0.0 1.0 (rgba)
DrawLine 71.6 80.0 to 71.6 53.0
DrawLine 77.6 80.0 to 77.6 53.0
SetPenColor 0.0 0.0 0.0 1.0 (rgba)
DrawLine 74.6 80.0 to 51.3 93.5
SetPenColor 0.4 0.4 0.4 1.0 (rgba)
SetPenColor 0.4 0.4 0.4 1.0 (rgba)
SetPenColor 1.0 0.1 0.1 1.0 (rgba)
SetFontSize 16
SetFontSize 16
SetFontSize 16
DrawText 74.6 40.0 "O"
SetPenColor 0.1 0.9 0.1 1.0 (rgba)
SetFontSize 16
SetFontSize 16
SetFontSize 16
SetFontSize 16
DrawText 40.0 100.0 "Cl"
@PennRobotics
Copy link
Author

There are still plenty of opportunities for simplification or condensing, such as removing unnecessary float(...) calls or combining lines, e.g. scale = min(w/cw, h/ch)

The top line sets whether debug info is printed. DEBUG(...) can contain any number of arguments which are then passed to print when this line has a true condition. If the condition is false, a lambda takes the arguments and does nothing with them. Presto change-o!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment