faster, better
This commit is contained in:
parent
fa6fd464bc
commit
531bf5c286
1 changed files with 100 additions and 86 deletions
|
@ -604,8 +604,8 @@ proc polygon*(path: var Path, pos: Vec2, size: float32, sides: int) {.inline.} =
|
||||||
## Adds a n-sided regular polygon at (x, y) with the parameter size.
|
## Adds a n-sided regular polygon at (x, y) with the parameter size.
|
||||||
path.polygon(pos.x, pos.y, size, sides)
|
path.polygon(pos.x, pos.y, size, sides)
|
||||||
|
|
||||||
proc commandsToShapes*(path: Path, pixelScale: float32 = 1.0): seq[seq[Vec2]] =
|
proc commandsToShapes(path: Path, pixelScale: float32 = 1.0): seq[seq[Vec2]] =
|
||||||
## Converts SVG-like commands to line segments.
|
## Converts SVG-like commands to sequences of vectors.
|
||||||
var
|
var
|
||||||
start, at: Vec2
|
start, at: Vec2
|
||||||
shape: seq[Vec2]
|
shape: seq[Vec2]
|
||||||
|
@ -962,6 +962,91 @@ proc commandsToShapes*(path: Path, pixelScale: float32 = 1.0): seq[seq[Vec2]] =
|
||||||
if shape.len > 0:
|
if shape.len > 0:
|
||||||
result.add(shape)
|
result.add(shape)
|
||||||
|
|
||||||
|
proc shapesToSegments(shapes: seq[seq[Vec2]]): seq[(Segment, int16)] =
|
||||||
|
## Converts the shapes into a set of filtered segments with winding value.
|
||||||
|
var segmentCount: int
|
||||||
|
for shape in shapes:
|
||||||
|
segmentCount += shape.len - 1
|
||||||
|
|
||||||
|
for shape in shapes:
|
||||||
|
for segment in shape.segments:
|
||||||
|
if segment.at.y == segment.to.y: # Skip horizontal
|
||||||
|
continue
|
||||||
|
var
|
||||||
|
segment = segment
|
||||||
|
winding = 1.int16
|
||||||
|
if segment.at.y > segment.to.y:
|
||||||
|
swap(segment.at, segment.to)
|
||||||
|
winding = -1
|
||||||
|
|
||||||
|
result.add((segment, winding))
|
||||||
|
|
||||||
|
proc computeBounds(segments: seq[(Segment, int16)]): Rect =
|
||||||
|
## Compute the bounds of the segments.
|
||||||
|
var
|
||||||
|
xMin = float32.high
|
||||||
|
xMax = float32.low
|
||||||
|
yMin = float32.high
|
||||||
|
yMax = float32.low
|
||||||
|
for (segment, _) in segments:
|
||||||
|
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)
|
||||||
|
|
||||||
|
if xMin.isNaN() or xMax.isNaN() or yMin.isNaN() or yMax.isNaN():
|
||||||
|
discard
|
||||||
|
else:
|
||||||
|
result.x = xMin
|
||||||
|
result.y = yMin
|
||||||
|
result.w = xMax - xMin
|
||||||
|
result.h = yMax - yMin
|
||||||
|
|
||||||
|
proc computeBounds*(path: Path): Rect =
|
||||||
|
## Compute the bounds of the path.
|
||||||
|
path.commandsToShapes().shapesToSegments().computeBounds()
|
||||||
|
|
||||||
|
proc partitionSegments(
|
||||||
|
segments: seq[(Segment, int16)], top, height: int
|
||||||
|
): (seq[seq[(Segment, int16)]], uint32, uint32) =
|
||||||
|
## Puts segments into the height partitions they intersect with.
|
||||||
|
## Returns (partitions, startY, partitionHeight)
|
||||||
|
let
|
||||||
|
maxPartitions = max(1, height div 10).uint32
|
||||||
|
numPartitions = min(maxPartitions, max(1, segments.len div 10).uint32)
|
||||||
|
partitionHeight = height.uint32 div numPartitions
|
||||||
|
|
||||||
|
result[0].setLen(numPartitions)
|
||||||
|
result[1] = top.uint32
|
||||||
|
result[2] = partitionHeight
|
||||||
|
|
||||||
|
for (segment, winding) in segments:
|
||||||
|
if partitionHeight == 0:
|
||||||
|
result[0][0].add((segment, winding))
|
||||||
|
else:
|
||||||
|
var
|
||||||
|
atPartition = max(0, segment.at.y - top.float32).uint32
|
||||||
|
toPartition = max(0, ceil(segment.to.y - top.float32)).uint32
|
||||||
|
atPartition = atPartition div partitionHeight
|
||||||
|
toPartition = toPartition div partitionHeight
|
||||||
|
atPartition = clamp(atPartition, 0, result[0].high.uint32)
|
||||||
|
toPartition = clamp(toPartition, 0, result[0].high.uint32)
|
||||||
|
for i in atPartition .. toPartition:
|
||||||
|
result[0][i].add((segment, winding))
|
||||||
|
|
||||||
|
proc getIndexForY(
|
||||||
|
partitions: (seq[seq[(Segment, int16)]], uint32, uint32), y: int
|
||||||
|
): uint32 {.inline.} =
|
||||||
|
if partitions[2] == 0 or partitions[0].len == 1:
|
||||||
|
0.uint32
|
||||||
|
else:
|
||||||
|
min((y.uint32 - partitions[1]) div partitions[2], partitions[0].high.uint32)
|
||||||
|
|
||||||
proc quickSort(a: var seq[(float32, int16)], inl, inr: int) =
|
proc quickSort(a: var seq[(float32, int16)], inl, inr: int) =
|
||||||
## Sorts in place + faster than standard lib sort.
|
## Sorts in place + faster than standard lib sort.
|
||||||
var
|
var
|
||||||
|
@ -983,30 +1068,6 @@ proc quickSort(a: var seq[(float32, int16)], inl, inr: int) =
|
||||||
quickSort(a, inl, r)
|
quickSort(a, inl, r)
|
||||||
quickSort(a, l, inr)
|
quickSort(a, l, inr)
|
||||||
|
|
||||||
proc computeBounds(partitions: seq[seq[(Segment, int16)]]): Rect =
|
|
||||||
## Compute bounds of a shape segments with windings.
|
|
||||||
var
|
|
||||||
xMin = float32.high
|
|
||||||
xMax = float32.low
|
|
||||||
yMin = float32.high
|
|
||||||
yMax = float32.low
|
|
||||||
for partition in partitions:
|
|
||||||
for (segment, _) in partition:
|
|
||||||
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
|
|
||||||
|
|
||||||
proc shouldFill(windingRule: WindingRule, count: int): bool {.inline.} =
|
proc shouldFill(windingRule: WindingRule, count: int): bool {.inline.} =
|
||||||
## Should we fill based on the current winding rule and count?
|
## Should we fill based on the current winding rule and count?
|
||||||
case windingRule:
|
case windingRule:
|
||||||
|
@ -1015,49 +1076,12 @@ proc shouldFill(windingRule: WindingRule, count: int): bool {.inline.} =
|
||||||
of wrEvenOdd:
|
of wrEvenOdd:
|
||||||
count mod 2 != 0
|
count mod 2 != 0
|
||||||
|
|
||||||
proc partitionSegments(
|
|
||||||
shapes: seq[seq[Vec2]], height: int
|
|
||||||
): seq[seq[(Segment, int16)]] =
|
|
||||||
## Puts segments into the height partitions they intersect with.
|
|
||||||
var segmentCount: int
|
|
||||||
for shape in shapes:
|
|
||||||
segmentCount += shape.len - 1
|
|
||||||
|
|
||||||
let
|
|
||||||
maxPartitions = max(1, height div 10).uint32
|
|
||||||
numPartitions = min(maxPartitions, max(1, segmentCount div 10).uint32)
|
|
||||||
partitionHeight = (height.uint32 div numPartitions)
|
|
||||||
|
|
||||||
result.setLen(numPartitions)
|
|
||||||
for shape in shapes:
|
|
||||||
for segment in shape.segments:
|
|
||||||
if segment.at.y == segment.to.y: # Skip horizontal
|
|
||||||
continue
|
|
||||||
var
|
|
||||||
segment = segment
|
|
||||||
winding = 1.int16
|
|
||||||
if segment.at.y > segment.to.y:
|
|
||||||
swap(segment.at, segment.to)
|
|
||||||
winding = -1
|
|
||||||
|
|
||||||
if partitionHeight == 0:
|
|
||||||
result[0].add((segment, winding))
|
|
||||||
else:
|
|
||||||
var
|
|
||||||
atPartition = max(0, segment.at.y).uint32 div partitionHeight
|
|
||||||
toPartition = max(0, ceil(segment.to.y)).uint32 div partitionHeight
|
|
||||||
atPartition = clamp(atPartition, 0, result.high.uint32)
|
|
||||||
toPartition = clamp(toPartition, 0, result.high.uint32)
|
|
||||||
for i in atPartition .. toPartition:
|
|
||||||
result[i].add((segment, winding))
|
|
||||||
|
|
||||||
template computeCoverages(
|
template computeCoverages(
|
||||||
coverages: var seq[uint8],
|
coverages: var seq[uint8],
|
||||||
hits: var seq[(float32, int16)],
|
hits: var seq[(float32, int16)],
|
||||||
size: Vec2,
|
size: Vec2,
|
||||||
y: int,
|
y: int,
|
||||||
partitions: seq[seq[(Segment, int16)]],
|
partitions: (seq[seq[(Segment, int16)]], uint32, uint32),
|
||||||
partitionHeight: uint32,
|
|
||||||
windingRule: WindingRule
|
windingRule: WindingRule
|
||||||
) =
|
) =
|
||||||
const
|
const
|
||||||
|
@ -1066,16 +1090,10 @@ template computeCoverages(
|
||||||
offset = 1 / quality.float32
|
offset = 1 / quality.float32
|
||||||
initialOffset = offset / 2 + epsilon
|
initialOffset = offset / 2 + epsilon
|
||||||
|
|
||||||
let
|
|
||||||
partition =
|
|
||||||
if partitionHeight == 0 or partitions.len == 1:
|
|
||||||
0.uint32
|
|
||||||
else:
|
|
||||||
min(y.uint32 div partitionHeight, partitions.high.uint32)
|
|
||||||
|
|
||||||
zeroMem(coverages[0].addr, coverages.len)
|
zeroMem(coverages[0].addr, coverages.len)
|
||||||
|
|
||||||
# Do scanlines for this row
|
# Do scanlines for this row
|
||||||
|
let partition = getIndexForY(partitions, y)
|
||||||
var
|
var
|
||||||
yLine = y.float32 + initialOffset - offset
|
yLine = y.float32 + initialOffset - offset
|
||||||
numHits: int
|
numHits: int
|
||||||
|
@ -1083,7 +1101,7 @@ template computeCoverages(
|
||||||
yLine += offset
|
yLine += offset
|
||||||
let scanline = line(vec2(0, yLine), vec2(size.x, yLine))
|
let scanline = line(vec2(0, yLine), vec2(size.x, yLine))
|
||||||
numHits = 0
|
numHits = 0
|
||||||
for (segment, winding) in partitions[partition]:
|
for (segment, winding) in partitions[0][partition]:
|
||||||
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) and segment.to != at:
|
if scanline.intersects(segment, at) and segment.to != at:
|
||||||
|
@ -1150,19 +1168,18 @@ proc fillShapes(
|
||||||
windingRule: WindingRule,
|
windingRule: WindingRule,
|
||||||
blendMode: BlendMode
|
blendMode: BlendMode
|
||||||
) =
|
) =
|
||||||
let
|
|
||||||
rgbx = color.asRgbx()
|
|
||||||
partitions = partitionSegments(shapes, image.height)
|
|
||||||
partitionHeight = image.height.uint32 div partitions.len.uint32
|
|
||||||
|
|
||||||
# Figure out the total bounds of all the shapes,
|
# Figure out the total bounds of all the shapes,
|
||||||
# rasterize only within the total bounds
|
# rasterize only within the total bounds
|
||||||
let
|
let
|
||||||
bounds = computeBounds(partitions)
|
rgbx = color.asRgbx()
|
||||||
|
blender = blendMode.blender()
|
||||||
|
segments = shapes.shapesToSegments()
|
||||||
|
bounds = computeBounds(segments)
|
||||||
startX = max(0, bounds.x.int)
|
startX = max(0, bounds.x.int)
|
||||||
startY = max(0, bounds.y.int)
|
startY = max(0, bounds.y.int)
|
||||||
stopY = min(image.height, (bounds.y + bounds.h).int)
|
stopY = min(image.height, (bounds.y + bounds.h).int)
|
||||||
blender = blendMode.blender()
|
pathHeight = stopY - startY
|
||||||
|
partitions = partitionSegments(segments, startY, pathHeight)
|
||||||
|
|
||||||
var
|
var
|
||||||
coverages = newSeq[uint8](image.width)
|
coverages = newSeq[uint8](image.width)
|
||||||
|
@ -1175,7 +1192,6 @@ proc fillShapes(
|
||||||
image.wh,
|
image.wh,
|
||||||
y,
|
y,
|
||||||
partitions,
|
partitions,
|
||||||
partitionHeight,
|
|
||||||
windingRule
|
windingRule
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1264,17 +1280,16 @@ proc fillShapes(
|
||||||
shapes: seq[seq[Vec2]],
|
shapes: seq[seq[Vec2]],
|
||||||
windingRule: WindingRule
|
windingRule: WindingRule
|
||||||
) =
|
) =
|
||||||
let
|
|
||||||
partitions = partitionSegments(shapes, mask.height)
|
|
||||||
partitionHeight = mask.height.uint32 div partitions.len.uint32
|
|
||||||
|
|
||||||
# Figure out the total bounds of all the shapes,
|
# Figure out the total bounds of all the shapes,
|
||||||
# rasterize only within the total bounds
|
# rasterize only within the total bounds
|
||||||
let
|
let
|
||||||
bounds = computeBounds(partitions)
|
segments = shapes.shapesToSegments()
|
||||||
|
bounds = computeBounds(segments)
|
||||||
startX = max(0, bounds.x.int)
|
startX = max(0, bounds.x.int)
|
||||||
startY = max(0, bounds.y.int)
|
startY = max(0, bounds.y.int)
|
||||||
stopY = min(mask.height, (bounds.y + bounds.h).int)
|
stopY = min(mask.height, (bounds.y + bounds.h).int)
|
||||||
|
pathHeight = stopY - startY
|
||||||
|
partitions = partitionSegments(segments, startY, pathHeight)
|
||||||
|
|
||||||
when defined(amd64) and not defined(pixieNoSimd):
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
let maskerSimd = bmNormal.maskerSimd()
|
let maskerSimd = bmNormal.maskerSimd()
|
||||||
|
@ -1290,7 +1305,6 @@ proc fillShapes(
|
||||||
mask.wh,
|
mask.wh,
|
||||||
y,
|
y,
|
||||||
partitions,
|
partitions,
|
||||||
partitionHeight,
|
|
||||||
windingRule
|
windingRule
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue