0.0.18 more paths.nim stuff

This commit is contained in:
Ryan Oldenburg 2021-01-24 21:31:12 -06:00
parent 86de2a92fb
commit 0cb7b5ea3d
15 changed files with 330 additions and 336 deletions

View file

@ -1,4 +1,4 @@
version = "0.0.17" version = "0.0.18"
author = "Andre von Houck and Ryan Oldenburg" author = "Andre von Houck and Ryan Oldenburg"
description = "Full-featured 2d graphics library for Nim." description = "Full-featured 2d graphics library for Nim."
license = "MIT" license = "MIT"
@ -11,4 +11,4 @@ requires "chroma >= 0.2.1"
requires "zippy >= 0.3.5" requires "zippy >= 0.3.5"
requires "flatty >= 0.1.3" requires "flatty >= 0.1.3"
requires "nimsimd >= 0.4.6" requires "nimsimd >= 0.4.6"
requires "bumpy >= 1.0.0" requires "bumpy >= 1.0.1"

View file

@ -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) 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 ## Converts SVG-like commands to simpler polygon
var start, at, to, ctr, ctr2: Vec2 var
var prevCommand: PathCommandKind 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) = const errorMargin = 0.2
# 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)
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.} = proc compute(at, ctrl1, ctrl2, to: Vec2, t: float32): Vec2 {.inline.} =
pow(1 - t, 3) * at + pow(1 - t, 3) * at +
3 * pow(1 - t, 2) * t * ctrl1 + pow(1 - t, 2) * 3 * t * ctrl1 +
3 * (1 - t) * pow(t, 2) * ctrl2 + (1 - t) * 3 * pow(t, 2) * ctrl2 +
pow(t, 3) * to pow(t, 3) * to
var prev = at 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 let
tPrev = (i - 1).float32 / steps.float32 tPrev = (i - 1).float32 / steps.float32
t = i.float32 / steps.float32 t = i.float32 / steps.float32
@ -397,37 +396,47 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] =
midpoint = (prev + next) / 2 midpoint = (prev + next) / 2
error = (midpoint - halfway).length error = (midpoint - halfway).length
if error >= 0.25: if error >= errorMargin:
# Error too large, double precision for this step # Error too large, double precision for this step
discretize(i * 2 - 1, steps * 2) shape.discretize(i * 2 - 1, steps * 2)
discretize(i * 2, steps * 2) shape.discretize(i * 2, steps * 2)
else: else:
drawLine(prev, next) shape.add(segment(prev, next))
prev = next prev = next
discretize(1, 1) shape.discretize(1, 1)
proc drawQuad(p0, p1, p2: Vec2) = proc addQuadratic(shape: var seq[Segment], at, ctrl, to: 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
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, at, radii: Vec2,
rotation: float32, rotation: float32,
large, sweep: bool, large, sweep: bool,
@ -513,7 +522,7 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] =
var prev = at var prev = at
proc discretize(arc: ArcParams, i, steps: int) = proc discretize(shape: var seq[Segment], arc: ArcParams, i, steps: int) =
let let
step = arc.delta / steps.float32 step = arc.delta / steps.float32
aPrev = arc.theta + step * (i - 1).float32 aPrev = arc.theta + step * (i - 1).float32
@ -523,75 +532,82 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] =
midpoint = (prev + next) / 2 midpoint = (prev + next) / 2
error = (midpoint - halfway).length error = (midpoint - halfway).length
if error >= 0.25: if error >= errorMargin:
# Error too large, try again with doubled precision # Error too large, try again with doubled precision
discretize(arc, i * 2 - 1, steps * 2) shape.discretize(arc, i * 2 - 1, steps * 2)
discretize(arc, i * 2, steps * 2) shape.discretize(arc, i * 2, steps * 2)
else: else:
drawLine(prev, next) shape.add(segment(prev, next))
prev = next prev = next
let arc = endpointToCenterArcParams(at, radii, rotation, large, sweep, to) 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(): if command.numbers.len != command.kind.parameterCount():
raise newException(PixieError, "Invalid path") raise newException(PixieError, "Invalid path")
case command.kind case command.kind:
of Move: of Move:
at.x = command.numbers[0] at.x = command.numbers[0]
at.y = command.numbers[1] at.y = command.numbers[1]
start = at start = at
of Line: of Line:
to.x = command.numbers[0] let to = vec2(command.numbers[0], command.numbers[1])
to.y = command.numbers[1] shape.add(segment(at, to))
drawLine(at, to)
at = to
of VLine:
to.x = at.x
to.y = command.numbers[0]
drawLine(at, to)
at = to at = to
of HLine: of HLine:
to.x = command.numbers[0] let to = vec2(command.numbers[0], at.y)
to.y = at.y shape.add(segment(at, to))
drawLine(at, to)
at = to at = to
of Quad: of VLine:
var i = 0 let to = vec2(at.x, command.numbers[0])
while i < command.numbers.len: shape.add(segment(at, to))
ctr.x = command.numbers[i+0]
ctr.y = command.numbers[i+1]
to.x = command.numbers[i+2]
to.y = command.numbers[i+3]
drawQuad(at, ctr, to)
at = to
i += 4
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 at = to
of Cubic: of Cubic:
ctr.x = command.numbers[0] let
ctr.y = command.numbers[1] ctrl1 = vec2(command.numbers[0], command.numbers[1])
ctr2.x = command.numbers[2] ctrl2 = vec2(command.numbers[2], command.numbers[3])
ctr2.y = command.numbers[3] to = vec2(command.numbers[4], command.numbers[5])
to.x = command.numbers[4] shape.addCubic(at, ctrl1, ctrl2, to)
to.y = command.numbers[5]
drawCurve(at, ctr, ctr2, to)
at = to at = to
prevCtrl2 = ctrl2
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 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 TQuad:
let
to = vec2(command.numbers[0], 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 Arc: of Arc:
let let
@ -600,200 +616,194 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] =
large = command.numbers[3] == 1 large = command.numbers[3] == 1
sweep = command.numbers[4] == 1 sweep = command.numbers[4] == 1
to = vec2(command.numbers[5], command.numbers[6]) to = vec2(command.numbers[5], command.numbers[6])
drawArc(at, radii, rotation, large, sweep, to) shape.addArc(at, radii, rotation, large, sweep, to)
at = to at = to
of Close:
if at != start:
if prevCommand == Quad or prevCommand == TQuad:
drawQuad(at, ctr, start)
else:
drawLine(at, start)
if polygon.len > 0:
result.add(polygon)
polygon = newSeq[Vec2]()
at = start
of RMove: of RMove:
at.x += command.numbers[0] at.x += command.numbers[0]
at.y += command.numbers[1] at.y += command.numbers[1]
start = at start = at
of RLine: of RLine:
to.x = at.x + command.numbers[0] let to = vec2(at.x + command.numbers[0], at.y + command.numbers[1])
to.y = at.y + command.numbers[1] shape.add(segment(at, to))
drawLine(at, to)
at = to
of RVLine:
to.x = at.x
to.y = at.y + command.numbers[0]
drawLine(at, to)
at = to at = to
of RHLine: of RHLine:
to.x = at.x + command.numbers[0] let to = vec2(at.x + command.numbers[0], at.y)
to.y = at.y shape.add(segment(at, to))
drawLine(at, to)
at = to at = to
of RQuad: of RVLine:
ctr.x = at.x + command.numbers[0] let to = vec2(at.x, at.y + command.numbers[0])
ctr.y = at.y + command.numbers[1] shape.add(segment(at, to))
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 at = to
of RCubic: of RCubic:
ctr.x = at.x + command.numbers[0] let
ctr.y = at.y + command.numbers[1] ctrl1 = vec2(at.x + command.numbers[0], at.y + command.numbers[1])
ctr2.x = at.x + command.numbers[2] ctrl2 = vec2(at.x + command.numbers[2], at.y + command.numbers[3])
ctr2.y = at.y + command.numbers[3] to = vec2(at.x + command.numbers[4], at.y + command.numbers[5])
to.x = at.x + command.numbers[4] shape.addCubic(at, ctrl1, ctrl2, to)
to.y = at.y + command.numbers[5]
drawCurve(at, ctr, ctr2, to)
at = to at = to
prevCtrl2 = ctrl2
of RSCubic: of RSCubic:
if prevCommand in {Cubic, SCubic, RCubic, RSCubic}: let
ctr = 2 * at - ctr2 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: else:
ctr = at at
ctr2.x = at.x + command.numbers[0] shape.addCubic(at, ctrl1, ctrl2, to)
ctr2.y = at.y + command.numbers[1] at = to
to.x = at.x + command.numbers[2] prevCtrl2 = ctrl2
to.y = at.y + command.numbers[3]
drawCurve(at, ctr, ctr2, to) 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 at = to
else: of Close:
raise newException(ValueError, "not supported path command " & $command) if at != start:
shape.add(segment(at, start))
at = start
if shape.len > 0:
result.add(shape)
shape.setLen(0)
prevCommand = command.kind prevCommandKind = command.kind
if polygon.len > 0: if shape.len > 0:
result.add(polygon) result.add(shape)
iterator zipline*[T](s: seq[T]): (T, T) = proc strokeShapes(
## Return elements in pairs: (1st, 2nd), (2nd, 3rd) ... (nth, last). shapes: seq[seq[Segment]],
for i in 0 ..< s.len - 1: color: ColorRGBA,
yield(s[i], s[i + 1]) strokeWidth: float32,
windingRule: WindingRule
iterator segments*(s: seq[Vec2]): Segment = ): seq[seq[Segment]] =
## Return elements in pairs: (1st, 2nd), (2nd, 3rd) ... (last, 1st). if strokeWidth == 0:
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:
return return
proc min(a, b: Vec2): Vec2 =
result.x = min(a.x, b.x) let
result.y = min(a.y, b.y) widthLeft = strokeWidth / 2
proc max(a, b: Vec2): Vec2 = widthRight = strokeWidth / 2
result.x = max(a.x, b.x)
result.y = max(a.y, b.y) for shape in shapes:
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
var var
vMin = poly[0] points: seq[Vec2]
vMax = poly[0] back: seq[Vec2]
for v in poly: for segment in shape:
vMin = min(v, vMin) let
vMax = max(v, vMax) tangent = (segment.at - segment.to).normalize()
result.xy = vMin.floor normal = vec2(-tangent.y, tangent.x)
result.wh = (vMax - vMin).ceil 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
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.} {.push checks: off, stacktrace: off.}
proc fillPolygons*( proc fillShapes*(
image: Image, image: Image,
size: Vec2, shapes: seq[seq[Segment]],
polys: seq[seq[Vec2]],
color: ColorRGBA, color: ColorRGBA,
windingRule: WindingRule, windingRule: WindingRule,
quality = 4 quality = 4
) = ) =
var bounds = newSeq[Rect](polys.len) var sortedShapes = newSeq[seq[(Segment, bool)]](shapes.len)
for i, poly in polys: for i, sorted in sortedShapes.mpairs:
bounds[i] = computeBounds(poly) 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 const ep = 0.0001 * PI
proc scanLineHits( proc scanLineHits(
polys: seq[seq[Vec2]], shapes: seq[seq[(Segment, bool)]],
bounds: seq[Rect], bounds: seq[Rect],
hits: var seq[(float32, bool)], hits: var seq[(float32, bool)],
size: Vec2, size: Vec2,
@ -804,25 +814,17 @@ proc fillPolygons*(
let let
yLine = y.float32 + ep + shiftY 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] let bounds = bounds[i]
if bounds.y > y.float32 or bounds.y + bounds.h < y.float32: if bounds.y > y.float32 or bounds.y + bounds.h < y.float32:
continue continue
for line in poly.segments: for (segment, winding) in shape:
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)
# Lines often connect and we need them to not share starts and ends # Lines often connect and we need them to not share starts and ends
var at: Vec2 var at: Vec2
if line2.intersects(scan, at) and line2.to != at: if scanline.intersects(segment, at) and segment.to != at:
let hits.add((at.x.clamp(0, size.x), winding))
winding = line.at.y > line.to.y
x = at.x.clamp(0, size.x)
hits.add((x, winding))
hits.sort(proc(a, b: (float32, bool)): int = cmp(a[0], b[0])) 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. # Do scan lines for this row.
for m in 0 ..< quality: 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: if hits.len == 0:
continue continue
var var
@ -877,17 +879,15 @@ proc fillPolygons*(
{.pop.} {.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. ## Given some path, turns it into polys.
when type(path) is string: when type(path) is string:
commandsToPolygons(parsePath(path).commands) commandsToShapes(parsePath(path))
elif type(path) is Path: elif type(path) is Path:
commandsToPolygons(path.commands) commandsToShapes(path)
elif type(path) is seq[PathCommand]: elif type(path) is seq[seq[Segment]]:
commandsToPolygons(path)
elif type(path) is seq[seq[Vec2]]:
path path
proc fillPath*( proc fillPath*(
@ -896,9 +896,8 @@ proc fillPath*(
color: ColorRGBA, color: ColorRGBA,
windingRule = wrNonZero windingRule = wrNonZero
) = ) =
let let polys = parseSomePath(path)
polys = parseSomePath(path) image.fillShapes(polys, color, windingRule)
image.fillPolygons(image.wh, polys, color, windingRule)
proc fillPath*( proc fillPath*(
image: Image, image: Image,
@ -911,7 +910,7 @@ proc fillPath*(
for poly in polys.mitems: for poly in polys.mitems:
for i, p in poly.mpairs: for i, p in poly.mpairs:
poly[i] = p + pos poly[i] = p + pos
image.fillPolygons(image.wh, polys, color, windingRule) image.fillShapes(polys, color, windingRule)
proc fillPath*( proc fillPath*(
image: Image, image: Image,
@ -924,24 +923,7 @@ proc fillPath*(
for poly in polys.mitems: for poly in polys.mitems:
for i, p in poly.mpairs: for i, p in poly.mpairs:
poly[i] = mat * p poly[i] = mat * p
image.fillPolygons(image.wh, polys, color, windingRule) image.fillShapes(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)
proc strokePath*( proc strokePath*(
image: Image, image: Image,
@ -950,7 +932,13 @@ proc strokePath*(
strokeWidth: float32, strokeWidth: float32,
windingRule = wrNonZero windingRule = wrNonZero
) = ) =
image.strokePath(parsePath(path), color, strokeWidth) var strokeShapes = strokeShapes(
parseSomePath(path),
color,
strokeWidth,
windingRule
)
image.fillShapes(strokeShapes, color, windingRule)
proc strokePath*( proc strokePath*(
image: Image, image: Image,
@ -960,13 +948,16 @@ proc strokePath*(
pos: Vec2, pos: Vec2,
windingRule = wrNonZero windingRule = wrNonZero
) = ) =
var polys = parseSomePath(path) var strokeShapes = strokeShapes(
let (strokeL, strokeR) = (strokeWidth/2, strokeWidth/2) parseSomePath(path),
var polys2 = strokePolygons(polys, strokeL, strokeR) color,
for poly in polys2.mitems: strokeWidth,
for i, p in poly.mpairs: windingRule
poly[i] = p + pos )
image.fillPolygons(image.wh, polys2, color, windingRule) for shape in strokeShapes.mitems:
for segment in shape.mitems:
segment += pos
image.fillShapes(strokeShapes, color, windingRule)
proc strokePath*( proc strokePath*(
image: Image, image: Image,
@ -976,10 +967,13 @@ proc strokePath*(
mat: Mat3, mat: Mat3,
windingRule = wrNonZero windingRule = wrNonZero
) = ) =
var polys = parseSomePath(path) var strokeShapes = strokeShapes(
let (strokeL, strokeR) = (strokeWidth/2, strokeWidth/2) parseSomePath(path),
var polys2 = strokePolygons(polys, strokeL, strokeR) color,
for poly in polys2.mitems: strokeWidth,
for i, p in poly.mpairs: windingRule
poly[i] = mat * p )
image.fillPolygons(image.wh, polys2, color, windingRule) for shape in strokeShapes.mitems:
for segment in shape.mitems:
segment = mat * segment
image.fillShapes(strokeShapes, color, windingRule)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 990 B

After

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 990 B

After

Width:  |  Height:  |  Size: 981 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 347 KiB

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 4 KiB