commit
b2a345ca40
3 changed files with 49 additions and 43 deletions
|
@ -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)
|
||||||
|
|
|
@ -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.}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in a new issue