Merge pull request #330 from treeform/sweeps2
Sweeps: Fix jugged outline and streaking.
This commit is contained in:
commit
bc631f649c
5 changed files with 240 additions and 70 deletions
123
experiments/rounded_rect.nim
Normal file
123
experiments/rounded_rect.nim
Normal file
|
@ -0,0 +1,123 @@
|
|||
import benchy, chroma, pixie, pixie/internal, strformat
|
||||
import benchy, chroma, pixie
|
||||
|
||||
proc newRoundedRectImage1(w, h, r: int, color: Color): Image =
|
||||
result = newImage(w, h)
|
||||
let ctx = newContext(result)
|
||||
ctx.fillStyle = color(0, 1, 0, 1)
|
||||
let
|
||||
pos = vec2(0, 0)
|
||||
wh = vec2(w.float32, h.float32)
|
||||
r = r.float32
|
||||
ctx.fillRoundedRect(rect(pos, wh), r)
|
||||
|
||||
proc newRoundedRectImage15(w, h, r: int, color: Color): Image =
|
||||
let path = newPath()
|
||||
let
|
||||
pos = vec2(0, 0)
|
||||
wh = vec2(w.float32, h.float32)
|
||||
r = r.float32
|
||||
path.roundedRect(rect(pos, wh), r, r, r, r)
|
||||
result = path.fillImage(w, h, color(0, 1, 0, 1))
|
||||
|
||||
proc newRoundedRectImage2(w, h, r: int, color: Color): Image =
|
||||
result = newImage(w, h)
|
||||
result.fill(color)
|
||||
|
||||
let
|
||||
w1 = w - 1
|
||||
h1 = h - 1
|
||||
for y in 0 ..< r:
|
||||
for x in 0 ..< r:
|
||||
var a: float32 = 0
|
||||
for s in 0 ..< 5:
|
||||
let
|
||||
yc = y.float32 + s.float32 / 5 + (1 / 5 / 2)
|
||||
xc = r.float32 - sqrt(r.float32*r.float32 - (yc - r.float32) ^ 2)
|
||||
let mid = (x.float32 - xc + 1).clamp(0, 1)
|
||||
a += 1/5 * mid
|
||||
|
||||
if a < 1:
|
||||
var c = color
|
||||
c.a = a
|
||||
let cx = c.rgbx
|
||||
result.setRgbaUnsafe(x, y, cx)
|
||||
result.setRgbaUnsafe(w1 - x, y, cx)
|
||||
result.setRgbaUnsafe(w1 - x, h1 - y, cx)
|
||||
result.setRgbaUnsafe(x, h1 - y, cx)
|
||||
|
||||
proc newRoundedRectImage3(w, h, r: int, color: Color): Image =
|
||||
result = newImage(w, h)
|
||||
result.fill(color)
|
||||
|
||||
if r == 0:
|
||||
return
|
||||
|
||||
const
|
||||
q = 5
|
||||
qf = q.float32
|
||||
qoffset: float32 = (1 / qf / 2)
|
||||
|
||||
let
|
||||
r = r.clamp(0, min(w, h) div 2)
|
||||
rf = r.float32
|
||||
w1 = w - 1
|
||||
h1 = h - 1
|
||||
rgbx = color.rgbx
|
||||
channels = [rgbx.r.uint32, rgbx.g.uint32, rgbx.b.uint32, rgbx.a.uint32]
|
||||
|
||||
var coverage = newSeq[uint8](r)
|
||||
|
||||
for y in 0 ..< r:
|
||||
zeroMem(coverage[0].addr, coverage.len)
|
||||
var yf: float32 = y.float32 + qoffset
|
||||
for m in 0 ..< q:
|
||||
let hit = sqrt(rf^2 - yf^2)
|
||||
coverage[hit.int] += max((1 - (hit - hit.trunc)) * 255 / qf, 0).uint8
|
||||
for x in hit.int + 1 ..< r:
|
||||
coverage[x] += (255 div q).uint8
|
||||
yf += 1 / qf
|
||||
|
||||
for x in 0 ..< r:
|
||||
let coverage = 255 - coverage[x]
|
||||
if coverage != 255:
|
||||
var cx: ColorRGBX
|
||||
cx.r = ((channels[0] * coverage) div 255).uint8
|
||||
cx.g = ((channels[1] * coverage) div 255).uint8
|
||||
cx.b = ((channels[2] * coverage) div 255).uint8
|
||||
cx.a = ((channels[3] * coverage) div 255).uint8
|
||||
|
||||
let
|
||||
xn = r - x - 1
|
||||
yn = r - y - 1
|
||||
result.setRgbaUnsafe(xn, yn, cx)
|
||||
result.setRgbaUnsafe(w1 - xn, yn, cx)
|
||||
result.setRgbaUnsafe(w1 - xn, h1 - yn, cx)
|
||||
result.setRgbaUnsafe(xn, h1 - yn, cx)
|
||||
|
||||
const r = 16
|
||||
|
||||
let img1 = newRoundedRectImage1(200, 200, r, color(0, 1, 0, 1))
|
||||
img1.writeFile("rrect_current.png")
|
||||
let img2 = newRoundedRectImage3(200, 200, r, color(0, 1, 0, 1))
|
||||
img2.writeFile("rrect_new.png")
|
||||
|
||||
let (diffScore, diffImage) = diff(img1, img2)
|
||||
echo &"score: {diffScore}"
|
||||
diffImage.writeFile("rrect_diff.png")
|
||||
|
||||
timeIt "fill rounded rect via path 1":
|
||||
for i in 0 ..< 10:
|
||||
discard newRoundedRectImage1(200, 200, r, color(0, 1, 0, 1))
|
||||
|
||||
timeIt "fill rounded rect via path 1.5":
|
||||
for i in 0 ..< 10:
|
||||
discard newRoundedRectImage15(200, 200, r, color(0, 1, 0, 1))
|
||||
|
||||
timeIt "fill rounded rect via math 2":
|
||||
for i in 0 ..< 10:
|
||||
discard newRoundedRectImage2(200, 200, 50, color(0, 1, 0, 1))
|
||||
|
||||
timeIt "fill rounded rect via math 3":
|
||||
for i in 0 ..< 10:
|
||||
discard newRoundedRectImage3(200, 200, r, color(0, 1, 0, 1))
|
|
@ -64,6 +64,10 @@ var hourGlass = parsePath("""
|
|||
M 20 20 L 180 20 L 20 180 L 180 180 z
|
||||
""")
|
||||
|
||||
var hourGlass2 = parsePath("""
|
||||
M 20 20 L 180 20 L 20 180 L 180 180 z M 62 24 L 132 24 L 50 173 L 156 173 z
|
||||
""")
|
||||
|
||||
# Hole
|
||||
var hole = parsePath("""
|
||||
M 40 40 L 40 160 L 160 160 L 160 40 z
|
||||
|
@ -88,6 +92,8 @@ when defined(bench):
|
|||
test("cricle", cricle, 100)
|
||||
test("halfAarc", halfAarc, 100)
|
||||
test("hourGlass", hourGlass, 100)
|
||||
test("hourGlass2", hourGlass2, wr=wrNonZero)
|
||||
test("hourGlass2", hourGlass2, wr=wrEvenOdd)
|
||||
test("hole", hole, 100)
|
||||
test("holeEvenOdd", holeEvenOdd, 100, wr=wrNonZero)
|
||||
test("holeEvenOdd", holeEvenOdd, 100, wr=wrEvenOdd)
|
||||
|
@ -99,7 +105,8 @@ else:
|
|||
# test("cricle", cricle)
|
||||
# test("halfAarc", halfAarc)
|
||||
# test("hourGlass", hourGlass)
|
||||
#test("hole", hole, wr=wrEvenOdd)
|
||||
test("holeEvenOdd", holeEvenOdd, wr=wrNonZero)
|
||||
test("holeEvenOdd", holeEvenOdd, wr=wrEvenOdd)
|
||||
test("hourGlass2", hourGlass2, wr=wrEvenOdd)
|
||||
# test("hole", hole, wr=wrEvenOdd)
|
||||
# test("holeEvenOdd", holeEvenOdd, wr=wrNonZero)
|
||||
# test("holeEvenOdd", holeEvenOdd, wr=wrEvenOdd)
|
||||
# test("letterG", letterG)
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 5 KiB After Width: | Height: | Size: 4.4 KiB |
|
@ -2,6 +2,7 @@
|
|||
|
||||
import chroma, pixie/common, pixie/images, pixie/internal, pixie/paints,
|
||||
pixie/paths, strutils, tables, vmath, xmlparser, xmltree
|
||||
|
||||
when defined(pixieDebugSvg):
|
||||
import strtabs
|
||||
|
||||
|
|
|
@ -2103,6 +2103,17 @@ when defined(pixieSweeps):
|
|||
line.winding = s[1]
|
||||
return line
|
||||
|
||||
proc intersectsYLine(y: float32, s: Segment, atx: var float32): bool {.inline.} =
|
||||
let
|
||||
s2y = s.to.y - s.at.y
|
||||
denominator = -s2y
|
||||
numerator = s.at.y - y
|
||||
u = numerator / denominator
|
||||
if u >= 0 and u <= 1:
|
||||
let at = s.at + (u * vec2(s.to.x - s.at.x, s2y))
|
||||
atx = at.x
|
||||
return true
|
||||
|
||||
proc binaryInsert(arr: var seq[float32], v: float32) =
|
||||
if arr.len == 0:
|
||||
arr.add(v)
|
||||
|
@ -2195,16 +2206,23 @@ when defined(pixieSweeps):
|
|||
windingRule: WindingRule,
|
||||
blendMode: BlendMode
|
||||
) =
|
||||
const q = 1/256.0
|
||||
|
||||
let rgbx = color.rgbx
|
||||
var segments = shapes.shapesToSegments()
|
||||
let
|
||||
bounds = computeBounds(segments).snapToPixels()
|
||||
startX = max(0, bounds.x.int)
|
||||
|
||||
if segments.len == 0:
|
||||
if segments.len == 0 or bounds.w.int == 0 or bounds.h.int == 0:
|
||||
return
|
||||
|
||||
# const q = 1/10
|
||||
# for i in 0 ..< segments.len:
|
||||
# segments[i][0].at.x = quantize(segments[i][0].at.x, q)
|
||||
# segments[i][0].at.y = quantize(segments[i][0].at.y, q)
|
||||
# segments[i][0].to.x = quantize(segments[i][0].to.x, q)
|
||||
# segments[i][0].to.y = quantize(segments[i][0].to.y, q)
|
||||
|
||||
# Create sorted segments.
|
||||
segments.sortSegments(0, segments.high)
|
||||
|
||||
|
@ -2227,14 +2245,13 @@ when defined(pixieSweeps):
|
|||
let s = segments[lastSeg]
|
||||
|
||||
if s[0].to.y != cutLines[i + 1]:
|
||||
var at: Vec2
|
||||
var atx: float32
|
||||
var seg = s[0]
|
||||
for j in i ..< sweeps.len:
|
||||
let y = cutLines[j + 1]
|
||||
#TODO: speed up with horizintal line intersect
|
||||
if intersects(line(vec2(0, y), vec2(1, y)), seg, at):
|
||||
sweeps[j].add(toLine((segment(seg.at, at), s[1])))
|
||||
seg = segment(at, seg.to)
|
||||
if intersectsYLine(y, seg, atx):
|
||||
sweeps[j].add(toLine((segment(seg.at, vec2(atx, y)), s[1])))
|
||||
seg = segment(vec2(atx, y), seg.to)
|
||||
else:
|
||||
if seg.at.y != seg.to.y:
|
||||
sweeps[j].add(toLine(s))
|
||||
|
@ -2247,40 +2264,49 @@ when defined(pixieSweeps):
|
|||
break
|
||||
inc i
|
||||
|
||||
i = 0
|
||||
while i < sweeps.len:
|
||||
# TODO: Maybe finds all cuts first, add them to array, cut all lines at once.
|
||||
for t in 0 ..< 10: # TODO: maybe while true:
|
||||
# keep cutting sweep
|
||||
var needsCut = false
|
||||
var cutterLine: float32 = 0
|
||||
block doubleFor:
|
||||
for a in sweeps[i]:
|
||||
let aSeg = segment(vec2(a.atx, cutLines[i]), vec2(a.tox, cutLines[i+1]))
|
||||
for b in sweeps[i]:
|
||||
let bSeg = segment(vec2(b.atx, cutLines[i]), vec2(b.tox, cutLines[i+1]))
|
||||
var at: Vec2
|
||||
if intersectsInner(aSeg, bSeg, at):
|
||||
needsCut = true
|
||||
cutterLine = at.y
|
||||
break doubleFor
|
||||
# TODO enable?
|
||||
if false and needsCut:
|
||||
# Doing a cut.
|
||||
var
|
||||
thisSweep = sweeps[i]
|
||||
sweeps[i].setLen(0)
|
||||
sweeps.insert(newSeq[SweepLine](), i + 1)
|
||||
for a in thisSweep:
|
||||
let seg = segment(vec2(a.atx, cutLines[i]), vec2(a.tox, cutLines[i+1]))
|
||||
var at: Vec2
|
||||
if intersects(line(vec2(0, cutterLine), vec2(1, cutterLine)), seg, at):
|
||||
sweeps[i+0].add(toLine((segment(seg.at, at), a.winding)))
|
||||
sweeps[i+1].add(toLine((segment(at, seg.to), a.winding)))
|
||||
cutLines.binaryInsert(cutterLine)
|
||||
else:
|
||||
break
|
||||
inc i
|
||||
# i = 0
|
||||
# while i < sweeps.len:
|
||||
# # TODO: Maybe finds all cuts first, add them to array, cut all lines at once.
|
||||
# var crossCuts: seq[float32]
|
||||
|
||||
# # echo i, " cut?"
|
||||
|
||||
# for aIndex in 0 ..< sweeps[i].len:
|
||||
# let a = sweeps[i][aIndex]
|
||||
# # echo i, ":", sweeps.len, ":", cutLines.len
|
||||
# let aSeg = segment(vec2(a.atx, cutLines[i]), vec2(a.tox, cutLines[i+1]))
|
||||
# for bIndex in aIndex + 1 ..< sweeps[i].len:
|
||||
# let b = sweeps[i][bIndex]
|
||||
# let bSeg = segment(vec2(b.atx, cutLines[i]), vec2(b.tox, cutLines[i+1]))
|
||||
# var at: Vec2
|
||||
# if intersectsInner(aSeg, bSeg, at):
|
||||
# crossCuts.binaryInsert(at.y)
|
||||
|
||||
# if crossCuts.len > 0:
|
||||
# var
|
||||
# thisSweep = sweeps[i]
|
||||
# yTop = cutLines[i]
|
||||
# yBottom = cutLines[i + 1]
|
||||
# sweeps[i].setLen(0)
|
||||
|
||||
# for k in crossCuts:
|
||||
# let prevLen = cutLines.len
|
||||
# cutLines.binaryInsert(k)
|
||||
# if prevLen != cutLines.len:
|
||||
# sweeps.insert(newSeq[SweepLine](), i + 1)
|
||||
|
||||
# for a in thisSweep:
|
||||
# var seg = segment(vec2(a.atx, yTop), vec2(a.tox, yBottom))
|
||||
# var at: Vec2
|
||||
# for j, cutterLine in crossCuts:
|
||||
# if intersects(line(vec2(0, cutterLine), vec2(1, cutterLine)), seg, at):
|
||||
# sweeps[i+j].add(toLine((segment(seg.at, at), a.winding)))
|
||||
# seg = segment(at, seg.to)
|
||||
# sweeps[i+crossCuts.len].add(toLine((seg, a.winding)))
|
||||
|
||||
# i += crossCuts.len
|
||||
|
||||
# inc i
|
||||
|
||||
i = 0
|
||||
while i < sweeps.len:
|
||||
|
@ -2320,13 +2346,18 @@ when defined(pixieSweeps):
|
|||
# echo "L ", sw.x, " ", sw.y
|
||||
|
||||
proc computeCoverage(
|
||||
coverages: var seq[uint8],
|
||||
coverages: var seq[uint16],
|
||||
y: int,
|
||||
startX: int,
|
||||
cutLines: seq[float32],
|
||||
currCutLine: int,
|
||||
sweep: seq[SweepLine]
|
||||
) =
|
||||
|
||||
if cutLines[currCutLine + 1] - cutLines[currCutLine] < 1/256:
|
||||
# TODO some thing about micro sweeps
|
||||
return
|
||||
|
||||
let
|
||||
sweepHeight = cutLines[currCutLine + 1] - cutLines[currCutLine]
|
||||
yFracTop = ((y.float32 - cutLines[currCutLine]) / sweepHeight).clamp(0, 1)
|
||||
|
@ -2341,59 +2372,67 @@ when defined(pixieSweeps):
|
|||
swX = mix(sweep[i+0].atx, sweep[i+0].tox, yFracBottom)
|
||||
seX = mix(sweep[i+1].atx, sweep[i+1].tox, yFracBottom)
|
||||
|
||||
minWi = min(nwX, swX).int
|
||||
maxWi = max(nwX, swX).ceil.int
|
||||
minWi = min(nwX, swX).int#.clamp(startX, coverages.len + startX)
|
||||
maxWi = max(nwX, swX).ceil.int#.clamp(startX, coverages.len + startX)
|
||||
|
||||
minEi = min(neX, seX).int
|
||||
maxEi = max(neX, seX).ceil.int
|
||||
|
||||
# TODO: Add case when trapezoids both starts and stops on same pixle.
|
||||
minEi = min(neX, seX).int#.clamp(startX, coverages.len + startX)
|
||||
maxEi = max(neX, seX).ceil.int#.clamp(startX, coverages.len + startX)
|
||||
|
||||
let
|
||||
nw = vec2(sweep[i+0].atx, cutLines[currCutLine])
|
||||
sw = vec2(sweep[i+0].tox, cutLines[currCutLine + 1])
|
||||
f16 = (256 * 256 - 1).float32
|
||||
for x in minWi ..< maxWi:
|
||||
var area = pixelCover(nw - vec2(x.float32, y.float32), sw - vec2(
|
||||
x.float32, y.float32))
|
||||
coverages[x - startX] += (area * 255).uint8
|
||||
var area = pixelCover(
|
||||
nw - vec2(x.float32, y.float32),
|
||||
sw - vec2(x.float32, y.float32)
|
||||
)
|
||||
coverages[x - startX] += (area * f16).uint16
|
||||
|
||||
let x = maxWi
|
||||
var midArea = pixelCover(nw - vec2(x.float32, y.float32), sw - vec2(
|
||||
x.float32, y.float32))
|
||||
var midArea8 = (midArea * 255).uint8
|
||||
for x in maxWi ..< minEi:
|
||||
# TODO: Maybe try coverages of uint16 to prevent streeks in solid white fill?
|
||||
coverages[x - startX] += midArea8
|
||||
var midArea = pixelCover(
|
||||
nw - vec2(x.float32, y.float32),
|
||||
sw - vec2(x.float32, y.float32)
|
||||
)
|
||||
for x in maxWi ..< maxEi:
|
||||
coverages[x - startX] += (midArea * f16).uint16
|
||||
|
||||
let
|
||||
ne = vec2(sweep[i+1].atx, cutLines[currCutLine])
|
||||
se = vec2(sweep[i+1].tox, cutLines[currCutLine + 1])
|
||||
for x in minEi ..< maxEi:
|
||||
var area = midArea - pixelCover(ne - vec2(x.float32, y.float32), se -
|
||||
vec2(x.float32, y.float32))
|
||||
coverages[x - startX] += (area * 255).uint8
|
||||
var area = pixelCover(
|
||||
ne - vec2(x.float32, y.float32),
|
||||
se - vec2(x.float32, y.float32)
|
||||
)
|
||||
coverages[x - startX] -= (area * f16).uint16
|
||||
|
||||
i += 2
|
||||
|
||||
var
|
||||
currCutLine = 0
|
||||
coverages = newSeq[uint8](bounds.w.int)
|
||||
for scanLine in cutLines[0].int ..< cutLines[^1].ceil.int:
|
||||
zeroMem(coverages[0].addr, coverages.len)
|
||||
coverages16 = newSeq[uint16](bounds.w.int)
|
||||
coverages8 = newSeq[uint8](bounds.w.int)
|
||||
for scanLine in max(cutLines[0].int, 0) ..< min(cutLines[^1].ceil.int, image.height):
|
||||
|
||||
coverages.computeCoverage(scanLine, startX, cutLines, currCutLine, sweeps[currCutLine])
|
||||
zeroMem(coverages16[0].addr, coverages16.len * 2)
|
||||
|
||||
coverages16.computeCoverage(
|
||||
scanLine, startX, cutLines, currCutLine, sweeps[currCutLine])
|
||||
while cutLines[currCutLine + 1] < scanLine.float + 1.0:
|
||||
inc currCutLine
|
||||
if currCutLine == sweeps.len:
|
||||
break
|
||||
coverages.computeCoverage(scanLine, startX, cutLines, currCutLine,
|
||||
sweeps[currCutLine])
|
||||
coverages16.computeCoverage(
|
||||
scanLine, startX, cutLines, currCutLine, sweeps[currCutLine])
|
||||
|
||||
for i in 0 ..< coverages16.len:
|
||||
coverages8[i] = (coverages16[i] shr 8).uint8
|
||||
image.fillCoverage(
|
||||
rgbx,
|
||||
startX = startX,
|
||||
y = scanLine,
|
||||
coverages,
|
||||
coverages8,
|
||||
blendMode
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue