0.0.18 more paths.nim stuff
|
@ -1,4 +1,4 @@
|
|||
version = "0.0.17"
|
||||
version = "0.0.18"
|
||||
author = "Andre von Houck and Ryan Oldenburg"
|
||||
description = "Full-featured 2d graphics library for Nim."
|
||||
license = "MIT"
|
||||
|
@ -11,4 +11,4 @@ requires "chroma >= 0.2.1"
|
|||
requires "zippy >= 0.3.5"
|
||||
requires "flatty >= 0.1.3"
|
||||
requires "nimsimd >= 0.4.6"
|
||||
requires "bumpy >= 1.0.0"
|
||||
requires "bumpy >= 1.0.1"
|
||||
|
|
|
@ -362,33 +362,32 @@ proc polygon*(path: var Path, x, y, size: float32, sides: int) =
|
|||
y + size * sin(side.float32 * 2.0 * PI / sides.float32)
|
||||
)
|
||||
|
||||
proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] =
|
||||
proc commandsToShapes*(path: Path): seq[seq[Segment]] =
|
||||
## Converts SVG-like commands to simpler polygon
|
||||
|
||||
var start, at, to, ctr, ctr2: Vec2
|
||||
var prevCommand: PathCommandKind
|
||||
var
|
||||
start, at: Vec2
|
||||
shape: seq[Segment]
|
||||
|
||||
var polygon: seq[Vec2]
|
||||
# Some commands use data from the previous command
|
||||
var
|
||||
prevCommandKind = Move
|
||||
prevCtrl, prevCtrl2: Vec2
|
||||
|
||||
proc drawLine(at, to: Vec2) =
|
||||
# Don't add any 0 length lines.
|
||||
if at - to != vec2(0, 0):
|
||||
# Don't double up points.
|
||||
if polygon.len == 0 or polygon[^1] != at:
|
||||
polygon.add(at)
|
||||
polygon.add(to)
|
||||
const errorMargin = 0.2
|
||||
|
||||
proc drawCurve(at, ctrl1, ctrl2, to: Vec2) =
|
||||
proc addCubic(shape: var seq[Segment], at, ctrl1, ctrl2, to: Vec2) =
|
||||
|
||||
proc compute(at, ctrl1, ctrl2, to: Vec2, t: float32): Vec2 {.inline.} =
|
||||
pow(1 - t, 3) * at +
|
||||
3 * pow(1 - t, 2) * t * ctrl1 +
|
||||
3 * (1 - t) * pow(t, 2) * ctrl2 +
|
||||
pow(1 - t, 2) * 3 * t * ctrl1 +
|
||||
(1 - t) * 3 * pow(t, 2) * ctrl2 +
|
||||
pow(t, 3) * to
|
||||
|
||||
var prev = at
|
||||
|
||||
proc discretize(i, steps: int) =
|
||||
proc discretize(shape: var seq[Segment], i, steps: int) =
|
||||
# Closure captures at, ctrl1, ctrl2, to and prev
|
||||
let
|
||||
tPrev = (i - 1).float32 / steps.float32
|
||||
t = i.float32 / steps.float32
|
||||
|
@ -397,37 +396,47 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] =
|
|||
midpoint = (prev + next) / 2
|
||||
error = (midpoint - halfway).length
|
||||
|
||||
if error >= 0.25:
|
||||
if error >= errorMargin:
|
||||
# Error too large, double precision for this step
|
||||
discretize(i * 2 - 1, steps * 2)
|
||||
discretize(i * 2, steps * 2)
|
||||
shape.discretize(i * 2 - 1, steps * 2)
|
||||
shape.discretize(i * 2, steps * 2)
|
||||
else:
|
||||
drawLine(prev, next)
|
||||
shape.add(segment(prev, next))
|
||||
prev = next
|
||||
|
||||
discretize(1, 1)
|
||||
shape.discretize(1, 1)
|
||||
|
||||
proc drawQuad(p0, p1, p2: Vec2) =
|
||||
let devx = p0.x - 2.0 * p1.x + p2.x
|
||||
let devy = p0.y - 2.0 * p1.y + p2.y
|
||||
let devsq = devx * devx + devy * devy
|
||||
if devsq < 0.333:
|
||||
drawLine(p0, p2)
|
||||
return
|
||||
let tol = 3.0
|
||||
let n = 1 + (tol * (devsq)).sqrt().sqrt().floor()
|
||||
var p = p0
|
||||
let nrecip = 1 / n
|
||||
var t = 0.0
|
||||
for i in 0 ..< int(n):
|
||||
t += nrecip
|
||||
let pn = lerp(lerp(p0, p1, t), lerp(p1, p2, t), t)
|
||||
drawLine(p, pn)
|
||||
p = pn
|
||||
proc addQuadratic(shape: var seq[Segment], at, ctrl, to: Vec2) =
|
||||
|
||||
drawLine(p, p2)
|
||||
proc compute(at, ctrl, to: Vec2, t: float32): Vec2 {.inline.} =
|
||||
pow(1 - t, 2) * at +
|
||||
2 * (1 - t) * t * ctrl +
|
||||
pow(t, 2) * to
|
||||
|
||||
proc drawArc(
|
||||
var prev = at
|
||||
|
||||
proc discretize(shape: var seq[Segment], i, steps: int) =
|
||||
# Closure captures at, ctrl, to and prev
|
||||
let
|
||||
tPrev = (i - 1).float32 / steps.float32
|
||||
t = i.float32 / steps.float32
|
||||
next = compute(at, ctrl, to, t)
|
||||
halfway = compute(at, ctrl, to, tPrev + (t - tPrev) / 2)
|
||||
midpoint = (prev + next) / 2
|
||||
error = (midpoint - halfway).length
|
||||
|
||||
if error >= errorMargin:
|
||||
# Error too large, double precision for this step
|
||||
shape.discretize(i * 2 - 1, steps * 2)
|
||||
shape.discretize(i * 2, steps * 2)
|
||||
else:
|
||||
shape.add(segment(prev, next))
|
||||
prev = next
|
||||
|
||||
shape.discretize(1, 1)
|
||||
|
||||
proc addArc(
|
||||
shape: var seq[Segment],
|
||||
at, radii: Vec2,
|
||||
rotation: float32,
|
||||
large, sweep: bool,
|
||||
|
@ -513,7 +522,7 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] =
|
|||
|
||||
var prev = at
|
||||
|
||||
proc discretize(arc: ArcParams, i, steps: int) =
|
||||
proc discretize(shape: var seq[Segment], arc: ArcParams, i, steps: int) =
|
||||
let
|
||||
step = arc.delta / steps.float32
|
||||
aPrev = arc.theta + step * (i - 1).float32
|
||||
|
@ -523,277 +532,278 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] =
|
|||
midpoint = (prev + next) / 2
|
||||
error = (midpoint - halfway).length
|
||||
|
||||
if error >= 0.25:
|
||||
if error >= errorMargin:
|
||||
# Error too large, try again with doubled precision
|
||||
discretize(arc, i * 2 - 1, steps * 2)
|
||||
discretize(arc, i * 2, steps * 2)
|
||||
shape.discretize(arc, i * 2 - 1, steps * 2)
|
||||
shape.discretize(arc, i * 2, steps * 2)
|
||||
else:
|
||||
drawLine(prev, next)
|
||||
shape.add(segment(prev, next))
|
||||
prev = next
|
||||
|
||||
let arc = endpointToCenterArcParams(at, radii, rotation, large, sweep, to)
|
||||
discretize(arc, 1, 1)
|
||||
shape.discretize(arc, 1, 1)
|
||||
|
||||
for command in commands:
|
||||
for command in path.commands:
|
||||
if command.numbers.len != command.kind.parameterCount():
|
||||
raise newException(PixieError, "Invalid path")
|
||||
|
||||
case command.kind
|
||||
of Move:
|
||||
at.x = command.numbers[0]
|
||||
at.y = command.numbers[1]
|
||||
start = at
|
||||
case command.kind:
|
||||
of Move:
|
||||
at.x = command.numbers[0]
|
||||
at.y = command.numbers[1]
|
||||
start = at
|
||||
|
||||
of Line:
|
||||
to.x = command.numbers[0]
|
||||
to.y = command.numbers[1]
|
||||
drawLine(at, to)
|
||||
at = to
|
||||
of Line:
|
||||
let to = vec2(command.numbers[0], command.numbers[1])
|
||||
shape.add(segment(at, to))
|
||||
at = to
|
||||
|
||||
of VLine:
|
||||
to.x = at.x
|
||||
to.y = command.numbers[0]
|
||||
drawLine(at, to)
|
||||
at = to
|
||||
of HLine:
|
||||
let to = vec2(command.numbers[0], at.y)
|
||||
shape.add(segment(at, to))
|
||||
at = to
|
||||
|
||||
of HLine:
|
||||
to.x = command.numbers[0]
|
||||
to.y = at.y
|
||||
drawLine(at, to)
|
||||
at = to
|
||||
of VLine:
|
||||
let to = vec2(at.x, command.numbers[0])
|
||||
shape.add(segment(at, to))
|
||||
at = to
|
||||
|
||||
of Quad:
|
||||
var i = 0
|
||||
while i < command.numbers.len:
|
||||
ctr.x = command.numbers[i+0]
|
||||
ctr.y = command.numbers[i+1]
|
||||
to.x = command.numbers[i+2]
|
||||
to.y = command.numbers[i+3]
|
||||
of Cubic:
|
||||
let
|
||||
ctrl1 = vec2(command.numbers[0], command.numbers[1])
|
||||
ctrl2 = vec2(command.numbers[2], command.numbers[3])
|
||||
to = vec2(command.numbers[4], command.numbers[5])
|
||||
shape.addCubic(at, ctrl1, ctrl2, to)
|
||||
at = to
|
||||
prevCtrl2 = ctrl2
|
||||
|
||||
drawQuad(at, ctr, to)
|
||||
at = to
|
||||
i += 4
|
||||
of SCubic:
|
||||
let
|
||||
ctrl2 = vec2(command.numbers[0], command.numbers[1])
|
||||
to = vec2(command.numbers[2], command.numbers[3])
|
||||
if prevCommandKind in {Cubic, SCubic, RCubic, RSCubic}:
|
||||
let ctrl1 = 2 * at - prevCtrl2
|
||||
shape.addCubic(at, ctrl1, ctrl2, to)
|
||||
else:
|
||||
shape.addCubic(at, at, ctrl2, to)
|
||||
at = to
|
||||
prevCtrl2 = ctrl2
|
||||
|
||||
of TQuad:
|
||||
if prevCommand != Quad and prevCommand != TQuad:
|
||||
ctr = at
|
||||
to.x = command.numbers[0]
|
||||
to.y = command.numbers[1]
|
||||
ctr = at - (ctr - at)
|
||||
drawQuad(at, ctr, to)
|
||||
at = to
|
||||
of Quad:
|
||||
let
|
||||
ctrl = vec2(command.numbers[0], command.numbers[1])
|
||||
to = vec2(command.numbers[2], command.numbers[3])
|
||||
shape.addQuadratic(at, ctrl, to)
|
||||
at = to
|
||||
prevCtrl = ctrl
|
||||
|
||||
of Cubic:
|
||||
ctr.x = command.numbers[0]
|
||||
ctr.y = command.numbers[1]
|
||||
ctr2.x = command.numbers[2]
|
||||
ctr2.y = command.numbers[3]
|
||||
to.x = command.numbers[4]
|
||||
to.y = command.numbers[5]
|
||||
drawCurve(at, ctr, ctr2, to)
|
||||
at = to
|
||||
|
||||
of Arc:
|
||||
let
|
||||
radii = vec2(command.numbers[0], command.numbers[1])
|
||||
rotation = command.numbers[2]
|
||||
large = command.numbers[3] == 1
|
||||
sweep = command.numbers[4] == 1
|
||||
to = vec2(command.numbers[5], command.numbers[6])
|
||||
drawArc(at, radii, rotation, large, sweep, to)
|
||||
at = to
|
||||
|
||||
of Close:
|
||||
if at != start:
|
||||
if prevCommand == Quad or prevCommand == TQuad:
|
||||
drawQuad(at, ctr, start)
|
||||
of TQuad:
|
||||
let
|
||||
to = vec2(command.numbers[0], command.numbers[1])
|
||||
ctrl =
|
||||
if prevCommandKind in {Quad, TQuad, RQuad, RTQuad}:
|
||||
2 * at - prevCtrl
|
||||
else:
|
||||
drawLine(at, start)
|
||||
if polygon.len > 0:
|
||||
result.add(polygon)
|
||||
polygon = newSeq[Vec2]()
|
||||
at
|
||||
shape.addQuadratic(at, ctrl, to)
|
||||
at = to
|
||||
prevCtrl = ctrl
|
||||
|
||||
of Arc:
|
||||
let
|
||||
radii = vec2(command.numbers[0], command.numbers[1])
|
||||
rotation = command.numbers[2]
|
||||
large = command.numbers[3] == 1
|
||||
sweep = command.numbers[4] == 1
|
||||
to = vec2(command.numbers[5], command.numbers[6])
|
||||
shape.addArc(at, radii, rotation, large, sweep, to)
|
||||
at = to
|
||||
|
||||
of RMove:
|
||||
at.x += command.numbers[0]
|
||||
at.y += command.numbers[1]
|
||||
start = at
|
||||
|
||||
of RLine:
|
||||
let to = vec2(at.x + command.numbers[0], at.y + command.numbers[1])
|
||||
shape.add(segment(at, to))
|
||||
at = to
|
||||
|
||||
of RHLine:
|
||||
let to = vec2(at.x + command.numbers[0], at.y)
|
||||
shape.add(segment(at, to))
|
||||
at = to
|
||||
|
||||
of RVLine:
|
||||
let to = vec2(at.x, at.y + command.numbers[0])
|
||||
shape.add(segment(at, to))
|
||||
at = to
|
||||
|
||||
of RCubic:
|
||||
let
|
||||
ctrl1 = vec2(at.x + command.numbers[0], at.y + command.numbers[1])
|
||||
ctrl2 = vec2(at.x + command.numbers[2], at.y + command.numbers[3])
|
||||
to = vec2(at.x + command.numbers[4], at.y + command.numbers[5])
|
||||
shape.addCubic(at, ctrl1, ctrl2, to)
|
||||
at = to
|
||||
prevCtrl2 = ctrl2
|
||||
|
||||
of RSCubic:
|
||||
let
|
||||
ctrl2 = vec2(at.x + command.numbers[0], at.y + command.numbers[1])
|
||||
to = vec2(at.x + command.numbers[2], at.y + command.numbers[3])
|
||||
ctrl1 =
|
||||
if prevCommandKind in {Cubic, SCubic, RCubic, RSCubic}:
|
||||
2 * at - prevCtrl2
|
||||
else:
|
||||
at
|
||||
shape.addCubic(at, ctrl1, ctrl2, to)
|
||||
at = to
|
||||
prevCtrl2 = ctrl2
|
||||
|
||||
of RQuad:
|
||||
let
|
||||
ctrl = vec2(at.x + command.numbers[0], at.y + command.numbers[1])
|
||||
to = vec2(at.x + command.numbers[2], at.y + command.numbers[3])
|
||||
shape.addQuadratic(at, ctrl, to)
|
||||
at = to
|
||||
prevCtrl = ctrl
|
||||
|
||||
of RTQuad:
|
||||
let
|
||||
to = vec2(at.x + command.numbers[0], at.y + command.numbers[1])
|
||||
ctrl =
|
||||
if prevCommandKind in {Quad, TQuad, RQuad, RTQuad}:
|
||||
2 * at - prevCtrl
|
||||
else:
|
||||
at
|
||||
shape.addQuadratic(at, ctrl, to)
|
||||
at = to
|
||||
prevCtrl = ctrl
|
||||
|
||||
of RArc:
|
||||
let
|
||||
radii = vec2(command.numbers[0], command.numbers[1])
|
||||
rotation = command.numbers[2]
|
||||
large = command.numbers[3] == 1
|
||||
sweep = command.numbers[4] == 1
|
||||
to = vec2(at.x + command.numbers[5], at.y + command.numbers[6])
|
||||
shape.addArc(at, radii, rotation, large, sweep, to)
|
||||
at = to
|
||||
|
||||
of Close:
|
||||
if at != start:
|
||||
shape.add(segment(at, start))
|
||||
at = start
|
||||
if shape.len > 0:
|
||||
result.add(shape)
|
||||
shape.setLen(0)
|
||||
|
||||
of RMove:
|
||||
at.x += command.numbers[0]
|
||||
at.y += command.numbers[1]
|
||||
start = at
|
||||
prevCommandKind = command.kind
|
||||
|
||||
of RLine:
|
||||
to.x = at.x + command.numbers[0]
|
||||
to.y = at.y + command.numbers[1]
|
||||
drawLine(at, to)
|
||||
at = to
|
||||
if shape.len > 0:
|
||||
result.add(shape)
|
||||
|
||||
of RVLine:
|
||||
to.x = at.x
|
||||
to.y = at.y + command.numbers[0]
|
||||
drawLine(at, to)
|
||||
at = to
|
||||
|
||||
of RHLine:
|
||||
to.x = at.x + command.numbers[0]
|
||||
to.y = at.y
|
||||
drawLine(at, to)
|
||||
at = to
|
||||
|
||||
of RQuad:
|
||||
ctr.x = at.x + command.numbers[0]
|
||||
ctr.y = at.y + command.numbers[1]
|
||||
to.x = at.x + command.numbers[2]
|
||||
to.y = at.y + command.numbers[3]
|
||||
drawQuad(at, ctr, to)
|
||||
at = to
|
||||
|
||||
of RTQuad:
|
||||
if prevCommand != RQuad and prevCommand != RTQuad:
|
||||
ctr = at
|
||||
to.x = at.x + command.numbers[0]
|
||||
to.y = at.y + command.numbers[1]
|
||||
ctr = at - (ctr - at)
|
||||
drawQuad(at, ctr, to)
|
||||
at = to
|
||||
|
||||
of RCubic:
|
||||
ctr.x = at.x + command.numbers[0]
|
||||
ctr.y = at.y + command.numbers[1]
|
||||
ctr2.x = at.x + command.numbers[2]
|
||||
ctr2.y = at.y + command.numbers[3]
|
||||
to.x = at.x + command.numbers[4]
|
||||
to.y = at.y + command.numbers[5]
|
||||
drawCurve(at, ctr, ctr2, to)
|
||||
at = to
|
||||
|
||||
of RSCubic:
|
||||
if prevCommand in {Cubic, SCubic, RCubic, RSCubic}:
|
||||
ctr = 2 * at - ctr2
|
||||
else:
|
||||
ctr = at
|
||||
ctr2.x = at.x + command.numbers[0]
|
||||
ctr2.y = at.y + command.numbers[1]
|
||||
to.x = at.x + command.numbers[2]
|
||||
to.y = at.y + command.numbers[3]
|
||||
drawCurve(at, ctr, ctr2, to)
|
||||
at = to
|
||||
|
||||
else:
|
||||
raise newException(ValueError, "not supported path command " & $command)
|
||||
|
||||
prevCommand = command.kind
|
||||
|
||||
if polygon.len > 0:
|
||||
result.add(polygon)
|
||||
|
||||
iterator zipline*[T](s: seq[T]): (T, T) =
|
||||
## Return elements in pairs: (1st, 2nd), (2nd, 3rd) ... (nth, last).
|
||||
for i in 0 ..< s.len - 1:
|
||||
yield(s[i], s[i + 1])
|
||||
|
||||
iterator segments*(s: seq[Vec2]): Segment =
|
||||
## Return elements in pairs: (1st, 2nd), (2nd, 3rd) ... (last, 1st).
|
||||
for i in 0 ..< s.len - 1:
|
||||
yield(Segment(at: s[i], to: s[i + 1]))
|
||||
# if s.len > 0:
|
||||
# let
|
||||
# first = s[0]
|
||||
# last = s[^1]
|
||||
# if first != last:
|
||||
# yield(Segment(at: s[^1], to: s[0]))
|
||||
|
||||
proc strokePolygons*(ps: seq[seq[Vec2]], strokeWidthR, strokeWidthL: float32): seq[seq[Vec2]] =
|
||||
## Converts simple polygons into stroked versions:
|
||||
# TODO: Stroke location, add caps and joins.
|
||||
for p in ps:
|
||||
var poly: seq[Vec2]
|
||||
var back: seq[Vec2] # Back side of poly.
|
||||
var prevRSeg: Segment
|
||||
var prevLSeg: Segment
|
||||
var first = true
|
||||
for (at, to) in p.zipline:
|
||||
let tangent = (at - to).normalize()
|
||||
let normal = vec2(-tangent.y, tangent.x)
|
||||
|
||||
var
|
||||
rSeg = segment(at + normal * strokeWidthR, to + normal * strokeWidthR)
|
||||
lSeg = segment(at - normal * strokeWidthL, to - normal * strokeWidthL)
|
||||
|
||||
if first:
|
||||
first = false
|
||||
# TODO: draw start cap
|
||||
else:
|
||||
var touch: Vec2
|
||||
if intersects(prevRSeg, rSeg, touch):
|
||||
rSeg.at = touch
|
||||
poly.setLen(poly.len - 1)
|
||||
else:
|
||||
discard # TODO: draw joint
|
||||
|
||||
if intersects(prevLSeg, lSeg, touch):
|
||||
lSeg.at = touch
|
||||
back.setLen(back.len - 1)
|
||||
else:
|
||||
discard # TODO: draw joint
|
||||
|
||||
poly.add rSeg.at
|
||||
back.add lSeg.at
|
||||
poly.add rSeg.to
|
||||
back.add lSeg.to
|
||||
|
||||
prevRSeg = rSeg
|
||||
prevLSeg = lSeg
|
||||
|
||||
# Add the backside reversed:
|
||||
for i in 1 .. back.len:
|
||||
poly.add back[^i]
|
||||
|
||||
# TODO: draw end cap
|
||||
# Cap it at the end:
|
||||
poly.add poly[0]
|
||||
|
||||
result.add(poly)
|
||||
|
||||
proc computeBounds(poly: seq[Vec2]): Rect =
|
||||
if poly.len == 0:
|
||||
proc strokeShapes(
|
||||
shapes: seq[seq[Segment]],
|
||||
color: ColorRGBA,
|
||||
strokeWidth: float32,
|
||||
windingRule: WindingRule
|
||||
): seq[seq[Segment]] =
|
||||
if strokeWidth == 0:
|
||||
return
|
||||
proc min(a, b: Vec2): Vec2 =
|
||||
result.x = min(a.x, b.x)
|
||||
result.y = min(a.y, b.y)
|
||||
proc max(a, b: Vec2): Vec2 =
|
||||
result.x = max(a.x, b.x)
|
||||
result.y = max(a.y, b.y)
|
||||
proc floor(a: Vec2): Vec2 =
|
||||
result.x = a.x.floor
|
||||
result.y = a.y.floor
|
||||
proc ceil(a: Vec2): Vec2 =
|
||||
result.x = a.x.ceil
|
||||
result.y = a.y.ceil
|
||||
|
||||
let
|
||||
widthLeft = strokeWidth / 2
|
||||
widthRight = strokeWidth / 2
|
||||
|
||||
for shape in shapes:
|
||||
var
|
||||
points: seq[Vec2]
|
||||
back: seq[Vec2]
|
||||
for segment in shape:
|
||||
let
|
||||
tangent = (segment.at - segment.to).normalize()
|
||||
normal = vec2(-tangent.y, tangent.x)
|
||||
left = segment(
|
||||
segment.at - normal * widthLeft,
|
||||
segment.to - normal * widthLeft
|
||||
)
|
||||
right = segment(
|
||||
segment.at + normal * widthRight,
|
||||
segment.to + normal * widthRight
|
||||
)
|
||||
|
||||
points.add([right.at, right.to])
|
||||
back.add([left.at, left.to])
|
||||
|
||||
# Add the back side reversed
|
||||
for i in 1 .. back.len:
|
||||
points.add(back[^i])
|
||||
|
||||
points.add(points[0])
|
||||
|
||||
# Walk the points to create the shape
|
||||
var strokeShape: seq[Segment]
|
||||
for i in 0 ..< points.len - 1:
|
||||
strokeShape.add(segment(points[i], points[i + 1]))
|
||||
|
||||
if strokeShape.len > 0:
|
||||
result.add(strokeShape)
|
||||
|
||||
proc computeBounds(shape: seq[Segment]): Rect =
|
||||
var
|
||||
vMin = poly[0]
|
||||
vMax = poly[0]
|
||||
for v in poly:
|
||||
vMin = min(v, vMin)
|
||||
vMax = max(v, vMax)
|
||||
result.xy = vMin.floor
|
||||
result.wh = (vMax - vMin).ceil
|
||||
xMin = float32.high
|
||||
xMax = float32.low
|
||||
yMin = float32.high
|
||||
yMax = float32.low
|
||||
for segment in shape:
|
||||
xMin = min(xMin, min(segment.at.x, segment.to.x))
|
||||
xMax = max(xMax, max(segment.at.x, segment.to.x))
|
||||
yMin = min(yMin, min(segment.at.y, segment.to.y))
|
||||
yMax = max(yMax, max(segment.at.y, segment.to.y))
|
||||
|
||||
xMin = floor(xMin)
|
||||
xMax = ceil(xMax)
|
||||
yMin = floor(yMin)
|
||||
yMax = ceil(yMax)
|
||||
|
||||
result.x = xMin
|
||||
result.y = yMin
|
||||
result.w = xMax - xMin
|
||||
result.h = yMax - yMin
|
||||
|
||||
{.push checks: off, stacktrace: off.}
|
||||
|
||||
proc fillPolygons*(
|
||||
proc fillShapes*(
|
||||
image: Image,
|
||||
size: Vec2,
|
||||
polys: seq[seq[Vec2]],
|
||||
shapes: seq[seq[Segment]],
|
||||
color: ColorRGBA,
|
||||
windingRule: WindingRule,
|
||||
quality = 4
|
||||
) =
|
||||
var bounds = newSeq[Rect](polys.len)
|
||||
for i, poly in polys:
|
||||
bounds[i] = computeBounds(poly)
|
||||
var sortedShapes = newSeq[seq[(Segment, bool)]](shapes.len)
|
||||
for i, sorted in sortedShapes.mpairs:
|
||||
for j, segment in shapes[i]:
|
||||
if segment.at.y == segment.to.y or segment.at - segment.to == Vec2():
|
||||
# Skip horizontal and zero-length
|
||||
continue
|
||||
var
|
||||
segment = segment
|
||||
winding = segment.at.y > segment.to.y
|
||||
if winding:
|
||||
swap(segment.at, segment.to)
|
||||
sorted.add((segment, winding))
|
||||
|
||||
# Compute the bounds of each shape
|
||||
var bounds = newSeq[Rect](shapes.len)
|
||||
for i, shape in shapes:
|
||||
bounds[i] = computeBounds(shape)
|
||||
|
||||
const ep = 0.0001 * PI
|
||||
|
||||
proc scanLineHits(
|
||||
polys: seq[seq[Vec2]],
|
||||
shapes: seq[seq[(Segment, bool)]],
|
||||
bounds: seq[Rect],
|
||||
hits: var seq[(float32, bool)],
|
||||
size: Vec2,
|
||||
|
@ -804,25 +814,17 @@ proc fillPolygons*(
|
|||
|
||||
let
|
||||
yLine = y.float32 + ep + shiftY
|
||||
scan = Line(a: vec2(-10000, yLine), b: vec2(10000, yLine))
|
||||
scanline = Line(a: vec2(-10000, yLine), b: vec2(10000, yLine))
|
||||
|
||||
for i, poly in polys:
|
||||
for i, shape in shapes:
|
||||
let bounds = bounds[i]
|
||||
if bounds.y > y.float32 or bounds.y + bounds.h < y.float32:
|
||||
continue
|
||||
for line in poly.segments:
|
||||
if line.at.y == line.to.y: # Skip horizontal lines
|
||||
continue
|
||||
var line2 = line
|
||||
if line2.at.y > line2.to.y: # Sort order doesn't actually matter
|
||||
swap(line2.at, line2.to)
|
||||
for (segment, winding) in shape:
|
||||
# Lines often connect and we need them to not share starts and ends
|
||||
var at: Vec2
|
||||
if line2.intersects(scan, at) and line2.to != at:
|
||||
let
|
||||
winding = line.at.y > line.to.y
|
||||
x = at.x.clamp(0, size.x)
|
||||
hits.add((x, winding))
|
||||
if scanline.intersects(segment, at) and segment.to != at:
|
||||
hits.add((at.x.clamp(0, size.x), winding))
|
||||
|
||||
hits.sort(proc(a, b: (float32, bool)): int = cmp(a[0], b[0]))
|
||||
|
||||
|
@ -835,7 +837,7 @@ proc fillPolygons*(
|
|||
|
||||
# Do scan lines for this row.
|
||||
for m in 0 ..< quality:
|
||||
polys.scanLineHits(bounds, hits, size, y, float32(m) / float32(quality))
|
||||
sortedShapes.scanLineHits(bounds, hits, image.wh, y, float32(m) / float32(quality))
|
||||
if hits.len == 0:
|
||||
continue
|
||||
var
|
||||
|
@ -877,17 +879,15 @@ proc fillPolygons*(
|
|||
|
||||
{.pop.}
|
||||
|
||||
type SomePath = seq[seq[Vec2]] | string | Path | seq[PathCommand]
|
||||
type SomePath = seq[seq[Segment]] | string | Path
|
||||
|
||||
proc parseSomePath(path: SomePath): seq[seq[Vec2]] =
|
||||
proc parseSomePath(path: SomePath): seq[seq[Segment]] =
|
||||
## Given some path, turns it into polys.
|
||||
when type(path) is string:
|
||||
commandsToPolygons(parsePath(path).commands)
|
||||
commandsToShapes(parsePath(path))
|
||||
elif type(path) is Path:
|
||||
commandsToPolygons(path.commands)
|
||||
elif type(path) is seq[PathCommand]:
|
||||
commandsToPolygons(path)
|
||||
elif type(path) is seq[seq[Vec2]]:
|
||||
commandsToShapes(path)
|
||||
elif type(path) is seq[seq[Segment]]:
|
||||
path
|
||||
|
||||
proc fillPath*(
|
||||
|
@ -896,9 +896,8 @@ proc fillPath*(
|
|||
color: ColorRGBA,
|
||||
windingRule = wrNonZero
|
||||
) =
|
||||
let
|
||||
polys = parseSomePath(path)
|
||||
image.fillPolygons(image.wh, polys, color, windingRule)
|
||||
let polys = parseSomePath(path)
|
||||
image.fillShapes(polys, color, windingRule)
|
||||
|
||||
proc fillPath*(
|
||||
image: Image,
|
||||
|
@ -911,7 +910,7 @@ proc fillPath*(
|
|||
for poly in polys.mitems:
|
||||
for i, p in poly.mpairs:
|
||||
poly[i] = p + pos
|
||||
image.fillPolygons(image.wh, polys, color, windingRule)
|
||||
image.fillShapes(polys, color, windingRule)
|
||||
|
||||
proc fillPath*(
|
||||
image: Image,
|
||||
|
@ -924,24 +923,7 @@ proc fillPath*(
|
|||
for poly in polys.mitems:
|
||||
for i, p in poly.mpairs:
|
||||
poly[i] = mat * p
|
||||
image.fillPolygons(image.wh, polys, color, windingRule)
|
||||
|
||||
proc strokePath*(
|
||||
image: Image,
|
||||
path: Path,
|
||||
color: ColorRGBA,
|
||||
strokeWidth: float32 = 1.0,
|
||||
windingRule = wrNonZero
|
||||
# TODO: Add more params:
|
||||
# strokeLocation: StrokeLocation,
|
||||
# strokeCap: StorkeCap,
|
||||
# strokeJoin: StorkeJoin
|
||||
) =
|
||||
let
|
||||
polys = parseSomePath(path)
|
||||
(strokeL, strokeR) = (strokeWidth/2, strokeWidth/2)
|
||||
polys2 = strokePolygons(polys, strokeL, strokeR)
|
||||
image.fillPath(polys2, color, windingRule)
|
||||
image.fillShapes(polys, color, windingRule)
|
||||
|
||||
proc strokePath*(
|
||||
image: Image,
|
||||
|
@ -950,7 +932,13 @@ proc strokePath*(
|
|||
strokeWidth: float32,
|
||||
windingRule = wrNonZero
|
||||
) =
|
||||
image.strokePath(parsePath(path), color, strokeWidth)
|
||||
var strokeShapes = strokeShapes(
|
||||
parseSomePath(path),
|
||||
color,
|
||||
strokeWidth,
|
||||
windingRule
|
||||
)
|
||||
image.fillShapes(strokeShapes, color, windingRule)
|
||||
|
||||
proc strokePath*(
|
||||
image: Image,
|
||||
|
@ -960,13 +948,16 @@ proc strokePath*(
|
|||
pos: Vec2,
|
||||
windingRule = wrNonZero
|
||||
) =
|
||||
var polys = parseSomePath(path)
|
||||
let (strokeL, strokeR) = (strokeWidth/2, strokeWidth/2)
|
||||
var polys2 = strokePolygons(polys, strokeL, strokeR)
|
||||
for poly in polys2.mitems:
|
||||
for i, p in poly.mpairs:
|
||||
poly[i] = p + pos
|
||||
image.fillPolygons(image.wh, polys2, color, windingRule)
|
||||
var strokeShapes = strokeShapes(
|
||||
parseSomePath(path),
|
||||
color,
|
||||
strokeWidth,
|
||||
windingRule
|
||||
)
|
||||
for shape in strokeShapes.mitems:
|
||||
for segment in shape.mitems:
|
||||
segment += pos
|
||||
image.fillShapes(strokeShapes, color, windingRule)
|
||||
|
||||
proc strokePath*(
|
||||
image: Image,
|
||||
|
@ -976,10 +967,13 @@ proc strokePath*(
|
|||
mat: Mat3,
|
||||
windingRule = wrNonZero
|
||||
) =
|
||||
var polys = parseSomePath(path)
|
||||
let (strokeL, strokeR) = (strokeWidth/2, strokeWidth/2)
|
||||
var polys2 = strokePolygons(polys, strokeL, strokeR)
|
||||
for poly in polys2.mitems:
|
||||
for i, p in poly.mpairs:
|
||||
poly[i] = mat * p
|
||||
image.fillPolygons(image.wh, polys2, color, windingRule)
|
||||
var strokeShapes = strokeShapes(
|
||||
parseSomePath(path),
|
||||
color,
|
||||
strokeWidth,
|
||||
windingRule
|
||||
)
|
||||
for shape in strokeShapes.mitems:
|
||||
for segment in shape.mitems:
|
||||
segment = mat * segment
|
||||
image.fillShapes(strokeShapes, color, windingRule)
|
||||
|
|
Before Width: | Height: | Size: 990 B After Width: | Height: | Size: 976 B |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 990 B After Width: | Height: | Size: 981 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 347 KiB After Width: | Height: | Size: 347 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 4 KiB |