Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch line-clipping algorithm #495

Merged
merged 2 commits into from
Oct 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 73 additions & 63 deletions datashader/glyphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,30 +145,6 @@ def extend(aggs, df, vt, bounds, plot_start=True):

# -- Helpers for computing line geometry --

# Outcode constants
INSIDE = 0b0000
LEFT = 0b0001
RIGHT = 0b0010
BOTTOM = 0b0100
TOP = 0b1000


@ngjit
def _compute_outcode(x, y, xmin, xmax, ymin, ymax):
"""Outcodes for Cohen-Sutherland"""
code = INSIDE

if x < xmin:
code |= LEFT
elif x > xmax:
code |= RIGHT
if y < ymin:
code |= BOTTOM
elif y > ymax:
code |= TOP
return code


def _build_map_onto_pixel(x_mapper, y_mapper):
@ngjit
def map_onto_pixel(vt, bounds, x, y):
Expand Down Expand Up @@ -237,6 +213,35 @@ def draw_line(x0i, y0i, x1i, y1i, i, plot_start, clipped, *aggs_and_cols):


def _build_extend_line(draw_line, map_onto_pixel):
@ngjit
def outside_bounds(x0, y0, x1, y1, xmin, xmax, ymin, ymax):
if x0 < xmin and x1 < xmin:
return True
if x0 > xmax and x1 > xmax:
return True
if y0 < ymin and y1 < ymin:
return True
return y0 > ymax and y1 > ymax

@ngjit
def clipt(p, q, t0, t1):
accept = True
if p < 0 and q < 0:
r = q / p
if r > t1:
accept = False
elif r > t0:
t0 = r
elif p > 0 and q < p:
r = q / p
if r < t0:
accept = False
elif r < t1:
t1 = r
elif q < 0:
accept = False
return t0, t1, accept

@ngjit
def extend_line(vt, bounds, xs, ys, plot_start, *aggs_and_cols):
"""Aggregate along a line formed by ``xs`` and ``ys``"""
Expand All @@ -256,50 +261,55 @@ def extend_line(vt, bounds, xs, ys, plot_start, *aggs_and_cols):
i += 1
continue

# Use Cohen-Sutherland to clip the segment to a bounding box
outcode0 = _compute_outcode(x0, y0, xmin, xmax, ymin, ymax)
outcode1 = _compute_outcode(x1, y1, xmin, xmax, ymin, ymax)
# Use Liang-Barsky (1992) to clip the segment to a bounding box
if outside_bounds(x0, y0, x1, y1, xmin, xmax, ymin, ymax):
plot_start = True
i += 1
continue

accept = False
clipped = False

while True:
if not (outcode0 | outcode1):
accept = True
break
elif outcode0 & outcode1:
plot_start = True
break
t0, t1 = 0, 1
dx = x1 - x0

t0, t1, accept = clipt(-dx, x0 - xmin, t0, t1)
if not accept:
i += 1
continue

t0, t1, accept = clipt(dx, xmax - x0, t0, t1)
if not accept:
i += 1
continue

dy = y1 - y0

t0, t1, accept = clipt(-dy, y0 - ymin, t0, t1)
if not accept:
i += 1
continue

t0, t1, accept = clipt(dy, ymax - y0, t0, t1)
if not accept:
i += 1
continue

if t1 < 1:
clipped = True
outcode_out = outcode0 if outcode0 else outcode1
if outcode_out & TOP:
x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0)
y = ymax
elif outcode_out & BOTTOM:
x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0)
y = ymin
elif outcode_out & RIGHT:
y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0)
x = xmax
elif outcode_out & LEFT:
y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0)
x = xmin

if outcode_out == outcode0:
x0, y0 = x, y
outcode0 = _compute_outcode(x0, y0, xmin, xmax, ymin, ymax)
# If x0 is clipped, we need to plot the new start
plot_start = True
else:
x1, y1 = x, y
outcode1 = _compute_outcode(x1, y1, xmin, xmax, ymin, ymax)

if accept:
x0i, y0i = map_onto_pixel(vt, bounds, x0, y0)
x1i, y1i = map_onto_pixel(vt, bounds, x1, y1)
draw_line(x0i, y0i, x1i, y1i, i, plot_start, clipped, *aggs_and_cols)
plot_start = False
x1 = x0 + t1 * dx
y1 = y0 + t1 * dy

if t0 > 0:
# If x0 is clipped, we need to plot the new start
clipped = True
plot_start = True
x0 = x0 + t0 * dx
y0 = y0 + t0 * dy

x0i, y0i = map_onto_pixel(vt, bounds, x0, y0)
x1i, y1i = map_onto_pixel(vt, bounds, x1, y1)
draw_line(x0i, y0i, x1i, y1i, i, plot_start, clipped, *aggs_and_cols)
plot_start = False
i += 1

return extend_line
21 changes: 21 additions & 0 deletions datashader/tests/test_glyphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,24 @@ def test_extend_lines_nan():
extend_line(vt, bounds, xs, ys, True, agg)
out = np.diag([1, 1, 0, 2, 0])
np.testing.assert_equal(agg, out)


def test_extend_lines_exact_bounds():
xs = np.array([-3, 1, 1, -3, -3])
ys = np.array([-3, -3, 1, 1, -3])

agg = np.zeros((4, 4), dtype='i4')
extend_line(vt, bounds, xs, ys, True, agg)
out = np.array([[2, 1, 1, 1],
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 1, 1, 1]])
np.testing.assert_equal(agg, out)

agg = np.zeros((4, 4), dtype='i4')
extend_line(vt, bounds, xs, ys, False, agg)
out = np.array([[1, 1, 1, 1],
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 1, 1, 1]])
np.testing.assert_equal(agg, out)