Merge pull request #23 from guzba/master

scanLineHits a bunch faster
This commit is contained in:
treeform 2020-12-04 16:32:17 -08:00 committed by GitHub
commit b2a345ca40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 49 additions and 43 deletions

View file

@ -12,20 +12,19 @@ proc segment*(at, to: Vec2): Segment =
result.at = at result.at = at
result.to = to result.to = to
proc intersects*(a, b: Segment, at: var Vec2): bool = proc intersects*(a, b: Segment, at: var Vec2): bool {.inline.} =
## Checks if the a segment intersects b segment. ## Checks if the a segment intersects b segment.
## If it returns true, at will have point of intersection ## If it returns true, at will have point of intersection
var s1x, s1y, s2x, s2y: float32 let
s1x = a.to.x - a.at.x s1x = a.to.x - a.at.x
s1y = a.to.y - a.at.y s1y = a.to.y - a.at.y
s2x = b.to.x - b.at.x s2x = b.to.x - b.at.x
s2y = b.to.y - b.at.y s2y = b.to.y - b.at.y
var s, t: float32 let
s = (-s1y * (a.at.x - b.at.x) + s1x * (a.at.y - b.at.y)) / denominator = (-s2x * s1y + s1x * s2y)
(-s2x * s1y + s1x * s2y) s = (-s1y * (a.at.x - b.at.x) + s1x * (a.at.y - b.at.y)) / denominator
t = (s2x * (a.at.y - b.at.y) - s2y * (a.at.x - b.at.x)) / t = (s2x * (a.at.y - b.at.y) - s2y * (a.at.x - b.at.x)) / denominator
(-s2x * s1y + s1x * s2y)
if s >= 0 and s < 1 and t >= 0 and t < 1: if s >= 0 and s < 1 and t >= 0 and t < 1:
at.x = a.at.x + (t * s1x) at.x = a.at.x + (t * s1x)

View file

@ -495,17 +495,17 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] =
if polygon.len > 0: if polygon.len > 0:
result.add(polygon) result.add(polygon)
iterator zipline*[T](s: seq[T]): (T, T) = iterator zipline[T](s: seq[T]): (T, T) =
## Return elements in pairs: (1st, 2nd), (2nd, 3rd) ... (nth, last). ## Return elements in pairs: (1st, 2nd), (2nd, 3rd) ... (nth, last).
for i in 0 ..< s.len - 1: for i in 0 ..< s.len - 1:
yield(s[i], s[i + 1]) yield(s[i], s[i + 1])
iterator zipwise*[T](s: seq[T]): (T, T) = iterator segments(s: seq[Vec2]): Segment =
## Return elements in pairs: (1st, 2nd), (2nd, 3rd) ... (last, 1st). ## Return elements in pairs: (1st, 2nd), (2nd, 3rd) ... (last, 1st).
for i in 0 ..< s.len - 1: for i in 0 ..< s.len - 1:
yield(s[i], s[i + 1]) yield(Segment(at: s[i], to: s[i + 1]))
if s.len > 0: if s.len > 0:
yield(s[^1], s[0]) yield(Segment(at: s[^1], to: s[0]))
proc strokePolygons*(ps: seq[seq[Vec2]], strokeWidthR, strokeWidthL: float32): seq[seq[Vec2]] = proc strokePolygons*(ps: seq[seq[Vec2]], strokeWidthR, strokeWidthL: float32): seq[seq[Vec2]] =
## Converts simple polygons into stroked versions: ## Converts simple polygons into stroked versions:
@ -587,11 +587,11 @@ proc computeBounds(polys: seq[seq[Vec2]]): Rect =
{.push checks: off, stacktrace: off.} {.push checks: off, stacktrace: off.}
proc fillPolygons*( proc fillPolygons*(
size: Vec2, size: Vec2,
polys: seq[seq[Vec2]], polys: seq[seq[Vec2]],
color: ColorRGBA, color: ColorRGBA,
quality = 4, quality = 4,
): Image = ): Image =
const ep = 0.0001 * PI const ep = 0.0001 * PI
result = newImage(size.x.int, size.y.int) result = newImage(size.x.int, size.y.int)
@ -599,32 +599,37 @@ proc fillPolygons*(
proc scanLineHits( proc scanLineHits(
polys: seq[seq[Vec2]], polys: seq[seq[Vec2]],
hits: var seq[(float32, bool)], hits: var seq[(float32, bool)],
size: Vec2,
y: int, y: int,
shiftY: float32 shiftY: float32
) = ) {.inline.} =
hits.setLen(0) hits.setLen(0)
var yLine = (float32(y) + ep) + shiftY
var scan = Segment(at: vec2(-10000, yLine), to: vec2(100000, yLine)) let
yLine = (float32(y) + ep) + shiftY
scan = Segment(at: vec2(-10000, yLine), to: vec2(100000, yLine))
for poly in polys: for poly in polys:
for (at, to) in poly.zipwise: for line in poly.segments:
let line = Segment(at: at, to: to)
var at: Vec2 var at: Vec2
if line.intersects(scan, at): if line.intersects(scan, at):
let winding = line.at.y > line.to.y let
let x = at.x.clamp(0, size.x) winding = line.at.y > line.to.y
x = at.x.clamp(0, size.x)
hits.add((x, winding)) 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]))
var hits: seq[(float32, bool)] var
hits = newSeq[(float32, bool)]()
var alphas = newSeq[float32](result.width) alphas = newSeq[float32](result.width)
for y in 0 ..< result.height: for y in 0 ..< result.height:
for x in 0 ..< result.width: # Reset alphas for this row.
alphas[x] = 0 zeroMem(alphas[0].addr, alphas.len * 4)
# Do scanlines for this row.
for m in 0 ..< quality: for m in 0 ..< quality:
polys.scanLineHits(hits, y, float32(m)/float32(quality)) polys.scanLineHits(hits, size, y, float32(m) / float32(quality))
if hits.len == 0: if hits.len == 0:
continue continue
var var
@ -633,12 +638,11 @@ proc fillPolygons*(
for x in 0 ..< result.width: for x in 0 ..< result.width:
var penEdge = penFill var penEdge = penFill
while true: while true:
if curHit >= hits.len: if curHit >= hits.len or x != hits[curHit][0].int:
break break
if x != hits[curHit][0].int: let
break cover = hits[curHit][0] - x.float32
let cover = hits[curHit][0] - x.float32 winding = hits[curHit][1]
let winding = hits[curHit][1]
if winding == false: if winding == false:
penFill += 1.0 penFill += 1.0
penEdge += 1.0 - cover penEdge += 1.0 - cover
@ -647,12 +651,12 @@ proc fillPolygons*(
penEdge -= 1.0 - cover penEdge -= 1.0 - cover
inc curHit inc curHit
alphas[x] += penEdge alphas[x] += penEdge
for x in 0 ..< result.width: for x in 0 ..< result.width:
var a = clamp(abs(alphas[x]) / float32(quality), 0.0, 1.0) let a = clamp(abs(alphas[x]) / float32(quality), 0.0, 1.0)
var colorWithAlpha = color var colorWithAlpha = color
colorWithAlpha.a = uint8(clamp(a, 0, 1) * 255.0) colorWithAlpha.a = uint8(a * 255.0)
result[x, y] = colorWithAlpha result.setRgbaUnsafe(x, y, colorWithAlpha)
# TODO: don't double-clamp and can probably be unsafe?
{.pop.} {.pop.}

View file

@ -3,4 +3,7 @@ import pixie/fileformats/svg, pixie
let let
original = readFile("tests/images/svg/Ghostscript_Tiger.svg") original = readFile("tests/images/svg/Ghostscript_Tiger.svg")
image = decodeSvg(original) image = decodeSvg(original)
image.writeFile("tests/images/svg/Ghostscript_Tiger.png") gold = readImage("tests/images/svg/Ghostscript_Tiger.png")
doAssert image.data == gold.data
# image.writeFile("tests/images/svg/Ghostscript_Tiger.png")