Merge pull request #322 from guzba/master
merge hits that stop and then start fill at same spot, other stuff
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
@ -1068,6 +1068,17 @@ proc computeBounds*(
|
||||||
shapes.transform(transform)
|
shapes.transform(transform)
|
||||||
computeBounds(shapes.shapesToSegments())
|
computeBounds(shapes.shapesToSegments())
|
||||||
|
|
||||||
|
proc initPartitionEntry(segment: Segment, winding: int16): PartitionEntry =
|
||||||
|
result.atY = segment.at.y
|
||||||
|
result.toY = segment.to.y
|
||||||
|
result.winding = winding
|
||||||
|
let d = segment.at.x - segment.to.x
|
||||||
|
if d == 0:
|
||||||
|
result.b = segment.at.x # Leave m = 0, store the x we want in b
|
||||||
|
else:
|
||||||
|
result.m = (segment.at.y - segment.to.y) / d
|
||||||
|
result.b = segment.at.y - result.m * segment.at.x
|
||||||
|
|
||||||
proc partitionSegments(
|
proc partitionSegments(
|
||||||
segments: seq[(Segment, int16)], top, height: int
|
segments: seq[(Segment, int16)], top, height: int
|
||||||
): Partitioning =
|
): Partitioning =
|
||||||
|
@ -1081,17 +1092,7 @@ proc partitionSegments(
|
||||||
result.partitionHeight = height.uint32 div numPartitions
|
result.partitionHeight = height.uint32 div numPartitions
|
||||||
|
|
||||||
for (segment, winding) in segments:
|
for (segment, winding) in segments:
|
||||||
var entry: PartitionEntry
|
let entry = initPartitionEntry(segment, winding)
|
||||||
entry.atY = segment.at.y
|
|
||||||
entry.toY = segment.to.y
|
|
||||||
entry.winding = winding
|
|
||||||
let d = segment.at.x - segment.to.x
|
|
||||||
if d == 0:
|
|
||||||
entry.b = segment.at.x # Leave m = 0, store the x we want in b
|
|
||||||
else:
|
|
||||||
entry.m = (segment.at.y - segment.to.y) / d
|
|
||||||
entry.b = segment.at.y - entry.m * segment.at.x
|
|
||||||
|
|
||||||
if result.partitionHeight == 0:
|
if result.partitionHeight == 0:
|
||||||
result.partitions[0].add(entry)
|
result.partitions[0].add(entry)
|
||||||
else:
|
else:
|
||||||
|
@ -1106,7 +1107,7 @@ proc partitionSegments(
|
||||||
result.partitions[i].add(entry)
|
result.partitions[i].add(entry)
|
||||||
|
|
||||||
proc getIndexForY(partitioning: Partitioning, y: int): uint32 {.inline.} =
|
proc getIndexForY(partitioning: Partitioning, y: int): uint32 {.inline.} =
|
||||||
if partitioning.partitionHeight == 0 or partitioning.partitions.len == 1:
|
if partitioning.partitions.len == 1:
|
||||||
0.uint32
|
0.uint32
|
||||||
else:
|
else:
|
||||||
min(
|
min(
|
||||||
|
@ -1114,39 +1115,43 @@ proc getIndexForY(partitioning: Partitioning, y: int): uint32 {.inline.} =
|
||||||
partitioning.partitions.high.uint32
|
partitioning.partitions.high.uint32
|
||||||
)
|
)
|
||||||
|
|
||||||
|
proc maxEntryCount(partitioning: Partitioning): int =
|
||||||
|
for i in 0 ..< partitioning.partitions.len:
|
||||||
|
result = max(result, partitioning.partitions[i].len)
|
||||||
|
|
||||||
proc insertionSort(
|
proc insertionSort(
|
||||||
a: var seq[(float32, int16)], lo, hi: int
|
hits: var seq[(float32, int16)], lo, hi: int
|
||||||
) {.inline.} =
|
) {.inline.} =
|
||||||
for i in lo + 1 .. hi:
|
for i in lo + 1 .. hi:
|
||||||
var
|
var
|
||||||
j = i - 1
|
j = i - 1
|
||||||
k = i
|
k = i
|
||||||
while j >= 0 and a[j][0] > a[k][0]:
|
while j >= 0 and hits[j][0] > hits[k][0]:
|
||||||
swap(a[j + 1], a[j])
|
swap(hits[j + 1], hits[j])
|
||||||
dec j
|
dec j
|
||||||
dec k
|
dec k
|
||||||
|
|
||||||
proc sort(a: var seq[(float32, int16)], inl, inr: int) =
|
proc sort(hits: var seq[(float32, int16)], inl, inr: int) =
|
||||||
## Quicksort + insertion sort, in-place and faster than standard lib sort.
|
## Quicksort + insertion sort, in-place and faster than standard lib sort.
|
||||||
let n = inr - inl + 1
|
let n = inr - inl + 1
|
||||||
if n < 32:
|
if n < 32:
|
||||||
insertionSort(a, inl, inr)
|
insertionSort(hits, inl, inr)
|
||||||
return
|
return
|
||||||
var
|
var
|
||||||
l = inl
|
l = inl
|
||||||
r = inr
|
r = inr
|
||||||
let p = a[l + n div 2][0]
|
let p = hits[l + n div 2][0]
|
||||||
while l <= r:
|
while l <= r:
|
||||||
if a[l][0] < p:
|
if hits[l][0] < p:
|
||||||
inc l
|
inc l
|
||||||
elif a[r][0] > p:
|
elif hits[r][0] > p:
|
||||||
dec r
|
dec r
|
||||||
else:
|
else:
|
||||||
swap(a[l], a[r])
|
swap(hits[l], hits[r])
|
||||||
inc l
|
inc l
|
||||||
dec r
|
dec r
|
||||||
sort(a, inl, r)
|
sort(hits, inl, r)
|
||||||
sort(a, l, inr)
|
sort(hits, l, inr)
|
||||||
|
|
||||||
proc shouldFill(
|
proc shouldFill(
|
||||||
windingRule: WindingRule, count: int
|
windingRule: WindingRule, count: int
|
||||||
|
@ -1166,28 +1171,37 @@ iterator walk(
|
||||||
width: float32
|
width: float32
|
||||||
): (float32, float32, int) =
|
): (float32, float32, int) =
|
||||||
var
|
var
|
||||||
|
i, count: int
|
||||||
prevAt: float32
|
prevAt: float32
|
||||||
count: int
|
while i < numHits:
|
||||||
for i in 0 ..< numHits:
|
|
||||||
let (at, winding) = hits[i]
|
let (at, winding) = hits[i]
|
||||||
if windingRule == wrNonZero and
|
|
||||||
(count != 0) == (count + winding != 0) and
|
|
||||||
i < numHits - 1:
|
|
||||||
# Shortcut: if nonzero rule, we only care about when the count changes
|
|
||||||
# between zero and nonzero (or the last hit)
|
|
||||||
count += winding
|
|
||||||
continue
|
|
||||||
if at > 0:
|
if at > 0:
|
||||||
if shouldFill(windingRule, count):
|
if shouldFill(windingRule, count):
|
||||||
|
if i < numHits - 1:
|
||||||
|
# Look ahead to see if the next hit is in the same spot as this hit.
|
||||||
|
# If it is, see if this hit and the next hit's windings cancel out.
|
||||||
|
# If they do, skip the hits. It will be yielded later in a
|
||||||
|
# larger chunk.
|
||||||
|
let (nextAt, nextWinding) = hits[i + 1]
|
||||||
|
if nextAt == at and winding + nextWinding == 0:
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
# Shortcut: we only care about when we stop filling (or the last hit).
|
||||||
|
# If we continue filling, move to next hit.
|
||||||
|
if windingRule == wrNonZero and count + winding != 0:
|
||||||
|
count += winding
|
||||||
|
inc i
|
||||||
|
continue
|
||||||
yield (prevAt, at, count)
|
yield (prevAt, at, count)
|
||||||
prevAt = at
|
prevAt = at
|
||||||
count += winding
|
count += winding
|
||||||
|
inc i
|
||||||
|
|
||||||
when defined(pixieLeakCheck):
|
when defined(pixieLeakCheck):
|
||||||
if prevAt != width and count != 0:
|
if prevAt != width and count != 0:
|
||||||
echo "Leak detected: ", count, " @ (", prevAt, ", ", y, ")"
|
echo "Leak detected: ", count, " @ (", prevAt, ", ", y, ")"
|
||||||
|
|
||||||
proc computeCoverages(
|
proc computeCoverage(
|
||||||
coverages: var seq[uint8],
|
coverages: var seq[uint8],
|
||||||
hits: var seq[(float32, int16)],
|
hits: var seq[(float32, int16)],
|
||||||
numHits: var int,
|
numHits: var int,
|
||||||
|
@ -1215,7 +1229,7 @@ proc computeCoverages(
|
||||||
yLine += offset
|
yLine += offset
|
||||||
numHits = 0
|
numHits = 0
|
||||||
for i in 0 ..< partitionEntryCount: # Perf
|
for i in 0 ..< partitionEntryCount: # Perf
|
||||||
let entry = partitioning.partitions[partitionIndex][i]
|
let entry = partitioning.partitions[partitionIndex][i].unsafeAddr # Perf
|
||||||
if entry.atY <= yLine and entry.toY >= yLine:
|
if entry.atY <= yLine and entry.toY >= yLine:
|
||||||
let x =
|
let x =
|
||||||
if entry.m == 0:
|
if entry.m == 0:
|
||||||
|
@ -1223,8 +1237,6 @@ proc computeCoverages(
|
||||||
else:
|
else:
|
||||||
(yLine - entry.b) / entry.m
|
(yLine - entry.b) / entry.m
|
||||||
|
|
||||||
if numHits == hits.len:
|
|
||||||
hits.setLen(hits.len * 2)
|
|
||||||
hits[numHits] = (min(x, width), entry.winding)
|
hits[numHits] = (min(x, width), entry.winding)
|
||||||
inc numHits
|
inc numHits
|
||||||
|
|
||||||
|
@ -1259,9 +1271,9 @@ proc computeCoverages(
|
||||||
when defined(amd64) and not defined(pixieNoSimd):
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
let sampleCoverageVec = mm_set1_epi8(cast[int8](sampleCoverage))
|
let sampleCoverageVec = mm_set1_epi8(cast[int8](sampleCoverage))
|
||||||
for _ in 0 ..< fillLen div 16:
|
for _ in 0 ..< fillLen div 16:
|
||||||
var coverage = mm_loadu_si128(coverages[i - startX].addr)
|
var coverageVec = mm_loadu_si128(coverages[i - startX].addr)
|
||||||
coverage = mm_add_epi8(coverage, sampleCoverageVec)
|
coverageVec = mm_add_epi8(coverageVec, sampleCoverageVec)
|
||||||
mm_storeu_si128(coverages[i - startX].addr, coverage)
|
mm_storeu_si128(coverages[i - startX].addr, coverageVec)
|
||||||
i += 16
|
i += 16
|
||||||
for j in i ..< fillStart + fillLen:
|
for j in i ..< fillStart + fillLen:
|
||||||
coverages[j - startX] += sampleCoverage
|
coverages[j - startX] += sampleCoverage
|
||||||
|
@ -1299,11 +1311,11 @@ proc fillCoverage(
|
||||||
for _ in 0 ..< coverages.len div 16:
|
for _ in 0 ..< coverages.len div 16:
|
||||||
let
|
let
|
||||||
index = image.dataIndex(x, y)
|
index = image.dataIndex(x, y)
|
||||||
coverage = mm_loadu_si128(coverages[x - startX].unsafeAddr)
|
coverageVec = mm_loadu_si128(coverages[x - startX].unsafeAddr)
|
||||||
|
|
||||||
if mm_movemask_epi8(mm_cmpeq_epi16(coverage, zeroVec)) != 0xffff:
|
if mm_movemask_epi8(mm_cmpeq_epi16(coverageVec, zeroVec)) != 0xffff:
|
||||||
# If the coverages are not all zero
|
# If the coverages are not all zero
|
||||||
if mm_movemask_epi8(mm_cmpeq_epi32(coverage, vec255)) == 0xffff:
|
if mm_movemask_epi8(mm_cmpeq_epi32(coverageVec, vec255)) == 0xffff:
|
||||||
# If the coverages are all 255
|
# If the coverages are all 255
|
||||||
if blendMode == bmNormal and rgbx.a == 255:
|
if blendMode == bmNormal and rgbx.a == 255:
|
||||||
for i in 0 ..< 4:
|
for i in 0 ..< 4:
|
||||||
|
@ -1317,9 +1329,9 @@ proc fillCoverage(
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Coverages are not all 255
|
# Coverages are not all 255
|
||||||
var coverage = coverage
|
var coverageVec = coverageVec
|
||||||
for i in 0 ..< 4:
|
for i in 0 ..< 4:
|
||||||
var unpacked = unpackAlphaValues(coverage)
|
var unpacked = unpackAlphaValues(coverageVec)
|
||||||
# Shift the coverages from `a` to `g` and `a` for multiplying
|
# Shift the coverages from `a` to `g` and `a` for multiplying
|
||||||
unpacked = mm_or_si128(unpacked, mm_srli_epi32(unpacked, 16))
|
unpacked = mm_or_si128(unpacked, mm_srli_epi32(unpacked, 16))
|
||||||
|
|
||||||
|
@ -1342,7 +1354,7 @@ proc fillCoverage(
|
||||||
blenderSimd(backdrop, source)
|
blenderSimd(backdrop, source)
|
||||||
)
|
)
|
||||||
|
|
||||||
coverage = mm_srli_si128(coverage, 4)
|
coverageVec = mm_srli_si128(coverageVec, 4)
|
||||||
|
|
||||||
elif blendMode == bmMask:
|
elif blendMode == bmMask:
|
||||||
for i in 0 ..< 4:
|
for i in 0 ..< 4:
|
||||||
|
@ -1534,11 +1546,11 @@ proc fillShapes(
|
||||||
|
|
||||||
var
|
var
|
||||||
coverages = newSeq[uint8](bounds.w.int)
|
coverages = newSeq[uint8](bounds.w.int)
|
||||||
hits = newSeq[(float32, int16)](4)
|
hits = newSeq[(float32, int16)](partitioning.maxEntryCount)
|
||||||
numHits: int
|
numHits: int
|
||||||
|
|
||||||
for y in startY ..< pathHeight:
|
for y in startY ..< pathHeight:
|
||||||
computeCoverages(
|
computeCoverage(
|
||||||
coverages,
|
coverages,
|
||||||
hits,
|
hits,
|
||||||
numHits,
|
numHits,
|
||||||
|
@ -1591,11 +1603,11 @@ proc fillShapes(
|
||||||
|
|
||||||
var
|
var
|
||||||
coverages = newSeq[uint8](bounds.w.int)
|
coverages = newSeq[uint8](bounds.w.int)
|
||||||
hits = newSeq[(float32, int16)](4)
|
hits = newSeq[(float32, int16)](partitioning.maxEntryCount)
|
||||||
numHits: int
|
numHits: int
|
||||||
|
|
||||||
for y in startY ..< pathHeight:
|
for y in startY ..< pathHeight:
|
||||||
computeCoverages(
|
computeCoverage(
|
||||||
coverages,
|
coverages,
|
||||||
hits,
|
hits,
|
||||||
numHits,
|
numHits,
|
||||||
|
@ -1937,10 +1949,7 @@ proc overlaps(
|
||||||
let
|
let
|
||||||
scanline = line(vec2(0, test.y), vec2(1000, test.y))
|
scanline = line(vec2(0, test.y), vec2(1000, test.y))
|
||||||
segments = shapes.shapesToSegments()
|
segments = shapes.shapesToSegments()
|
||||||
for i in 0 ..< segments.len: # For gc:arc
|
for (segment, winding) in segments:
|
||||||
let
|
|
||||||
segment = segments[i][0]
|
|
||||||
winding = segments[i][1]
|
|
||||||
if segment.at.y <= scanline.a.y and segment.to.y >= scanline.a.y:
|
if segment.at.y <= scanline.a.y and segment.to.y >= scanline.a.y:
|
||||||
var at: Vec2
|
var at: Vec2
|
||||||
if scanline.intersects(segment, at):
|
if scanline.intersects(segment, at):
|
||||||
|
@ -1950,8 +1959,7 @@ proc overlaps(
|
||||||
sort(hits, 0, hits.high)
|
sort(hits, 0, hits.high)
|
||||||
|
|
||||||
var count: int
|
var count: int
|
||||||
for i in 0 ..< hits.len: # For gc:arc
|
for (at, winding) in hits:
|
||||||
let (at, winding) = hits[i]
|
|
||||||
if at > test.x:
|
if at > test.x:
|
||||||
return shouldFill(windingRule, count)
|
return shouldFill(windingRule, count)
|
||||||
count += winding
|
count += winding
|
||||||
|
|
|
@ -5,8 +5,16 @@ let pathStr = "m57.611-8.591c-1.487,1.851-4.899,4.42-1.982,6.348,0.194,0.129,0.5
|
||||||
timeIt "parsePath":
|
timeIt "parsePath":
|
||||||
keep parsePath(pathStr)
|
keep parsePath(pathStr)
|
||||||
|
|
||||||
let image = newImage(500, 300)
|
block:
|
||||||
image.fill(rgba(255, 255, 255, 255))
|
let path = parsePath("""
|
||||||
|
M 10,30
|
||||||
|
A 20,20 0,0,1 50,30
|
||||||
|
A 20,20 0,0,1 90,30
|
||||||
|
Q 90,60 50,90
|
||||||
|
Q 10,60 10,30 z
|
||||||
|
""")
|
||||||
|
timeIt "fillOverlaps":
|
||||||
|
doAssert path.fillOverlaps(vec2(1, 1)) == false
|
||||||
|
|
||||||
timeIt "roundedRect":
|
timeIt "roundedRect":
|
||||||
const radius = 20
|
const radius = 20
|
||||||
|
@ -15,4 +23,5 @@ timeIt "roundedRect":
|
||||||
path.roundedRect(0.5, 0.5, 499, 299, radius, radius, radius, radius)
|
path.roundedRect(0.5, 0.5, 499, 299, radius, radius, radius, radius)
|
||||||
# path.roundedRect(0, 0, 500, 300, radius, radius, radius, radius)
|
# path.roundedRect(0, 0, 500, 300, radius, radius, radius, radius)
|
||||||
|
|
||||||
|
let image = newImage(500, 300)
|
||||||
image.fillPath(path, rgba(0, 0, 0, 255))
|
image.fillPath(path, rgba(0, 0, 0, 255))
|
||||||
|
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 260 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 2.4 MiB |
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 280 KiB |
Before Width: | Height: | Size: 644 KiB After Width: | Height: | Size: 644 KiB |
Before Width: | Height: | Size: 783 KiB After Width: | Height: | Size: 783 KiB |
Before Width: | Height: | Size: 3.4 MiB After Width: | Height: | Size: 3.4 MiB |
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 357 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 610 KiB After Width: | Height: | Size: 610 KiB |
Before Width: | Height: | Size: 519 KiB After Width: | Height: | Size: 519 KiB |
Before Width: | Height: | Size: 3.9 MiB After Width: | Height: | Size: 3.9 MiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1,002 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 893 B After Width: | Height: | Size: 836 B |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.1 KiB |