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"
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"

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)
)
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)

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