Merge pull request #81 from guzba/master

paths faster
This commit is contained in:
treeform 2021-01-28 19:03:55 -08:00 committed by GitHub
commit 77701751c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 84 additions and 57 deletions

View file

@ -142,36 +142,36 @@ proc subImage*(image: Image, x, y, w, h: int): Image =
w * 4 w * 4
) )
proc minifyBy2*(image: Image): Image = proc minifyBy2*(image: Image, power = 1): Image =
## Scales the image down by an integer scale. ## Scales the image down by an integer scale.
result = newImage(image.width div 2, image.height div 2) if power < 0:
for y in 0 ..< result.height: raise newException(PixieError, "Cannot minifyBy2 with negative power")
for x in 0 ..< result.width: if power == 0:
var color = return image.copy()
image.getRgbaUnsafe(x * 2 + 0, y * 2 + 0).color / 4.0 +
image.getRgbaUnsafe(x * 2 + 1, y * 2 + 0).color / 4.0 +
image.getRgbaUnsafe(x * 2 + 1, y * 2 + 1).color / 4.0 +
image.getRgbaUnsafe(x * 2 + 0, y * 2 + 1).color / 4.0
result.setRgbaUnsafe(x, y, color.rgba)
proc minifyBy2*(image: Image, scale2x: int): Image = for i in 1 .. power:
## Scales the image down by an integer scale. result = newImage(image.width div 2, image.height div 2)
result = image for y in 0 ..< result.height:
for i in 1 ..< scale2x: for x in 0 ..< result.width:
result = result.minifyBy2() var color =
image.getRgbaUnsafe(x * 2 + 0, y * 2 + 0).color / 4.0 +
image.getRgbaUnsafe(x * 2 + 1, y * 2 + 0).color / 4.0 +
image.getRgbaUnsafe(x * 2 + 1, y * 2 + 1).color / 4.0 +
image.getRgbaUnsafe(x * 2 + 0, y * 2 + 1).color / 4.0
result.setRgbaUnsafe(x, y, color.rgba)
proc magnifyBy2*(image: Image, scale2x: int): Image = proc magnifyBy2*(image: Image, power = 1): Image =
## Scales image image up by an integer scale. ## Scales image image up by 2 ^ power.
let scale = 2 ^ scale2x if power < 0:
raise newException(PixieError, "Cannot magnifyBy2 with negative power")
let scale = 2 ^ power
result = newImage(image.width * scale, image.height * scale) result = newImage(image.width * scale, image.height * scale)
for y in 0 ..< result.height: for y in 0 ..< result.height:
for x in 0 ..< result.width: for x in 0 ..< result.width:
var rgba = image.getRgbaUnsafe(x div scale, y div scale) var rgba = image.getRgbaUnsafe(x div scale, y div scale)
result.setRgbaUnsafe(x, y, rgba) result.setRgbaUnsafe(x, y, rgba)
proc magnifyBy2*(image: Image): Image =
image.magnifyBy2(2)
proc toPremultipliedAlpha*(image: Image) = proc toPremultipliedAlpha*(image: Image) =
## Converts an image to premultiplied alpha from straight alpha. ## Converts an image to premultiplied alpha from straight alpha.
var i: int var i: int
@ -492,15 +492,18 @@ proc draw*(a, b: Image, pos = vec2(0, 0), blendMode = bmNormal) {.inline.} =
a.draw(b, translate(pos), blendMode) a.draw(b, translate(pos), blendMode)
proc resize*(srcImage: Image, width, height: int): Image = proc resize*(srcImage: Image, width, height: int): Image =
result = newImage(width, height) if width == srcImage.width and height == srcImage.height:
result.draw( result = srcImage.copy()
srcImage, else:
scale(vec2( result = newImage(width, height)
(width + 1).float / srcImage.width.float, result.draw(
(height + 1).float / srcImage.height.float srcImage,
)), scale(vec2(
bmOverwrite width.float32 / srcImage.width.float32,
) height.float32 / srcImage.height.float32
)),
bmOverwrite
)
proc shift*(image: Image, offset: Vec2) = proc shift*(image: Image, offset: Vec2) =
## Shifts the image by offset. ## Shifts the image by offset.
@ -536,12 +539,15 @@ proc shadow*(
mask: Image, offset: Vec2, spread, blur: float32, color: ColorRGBA mask: Image, offset: Vec2, spread, blur: float32, color: ColorRGBA
): Image = ): Image =
## Create a shadow of the image with the offset, spread and blur. ## Create a shadow of the image with the offset, spread and blur.
# TODO: copying is bad here due to this being slow already,
# we're doing it tho to avoid mutating param and returning new Image.
let copy = mask.copy()
if offset != vec2(0, 0): if offset != vec2(0, 0):
mask.shift(offset) copy.shift(offset)
if spread > 0: if spread > 0:
mask.spread(spread) copy.spread(spread)
if blur > 0: if blur > 0:
mask.blurAlpha(blur) copy.blurAlpha(blur)
result = newImage(mask.width, mask.height) result = newImage(mask.width, mask.height)
result.fill(color) result.fill(color)
result.draw(mask, blendMode = bmMask) result.draw(copy, blendMode = bmMask)

View file

@ -744,17 +744,18 @@ 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(segments: seq[(Segment, int16)]): Rect = proc computeBounds(seqs: varargs[seq[(Segment, int16)]]): Rect =
var var
xMin = float32.high xMin = float32.high
xMax = float32.low xMax = float32.low
yMin = float32.high yMin = float32.high
yMax = float32.low yMax = float32.low
for (segment, _) in segments: for s in seqs:
xMin = min(xMin, min(segment.at.x, segment.to.x)) for (segment, _) in s:
xMax = max(xMax, max(segment.at.x, segment.to.x)) xMin = min(xMin, min(segment.at.x, segment.to.x))
yMin = min(yMin, min(segment.at.y, segment.to.y)) xMax = max(xMax, max(segment.at.x, segment.to.x))
yMax = max(yMax, max(segment.at.y, segment.to.y)) yMin = min(yMin, min(segment.at.y, segment.to.y))
yMax = max(yMax, max(segment.at.y, segment.to.y))
xMin = floor(xMin) xMin = floor(xMin)
xMax = ceil(xMax) xMax = ceil(xMax)
@ -772,22 +773,28 @@ proc fillShapes(
color: ColorRGBA, color: ColorRGBA,
windingRule: WindingRule windingRule: WindingRule
) = ) =
var sortedSegments: seq[(Segment, int16)] var topHalf, bottomHalf, fullHeight: seq[(Segment, int16)]
for shape in shapes: for shape in shapes:
for segment in shape.segments: for segment in shape.segments:
if segment.at.y == segment.to.y: # Skip horizontal if segment.at.y == segment.to.y: # Skip horizontal
continue continue
var
segment = segment
winding = 1.int16
if segment.at.y > segment.to.y: if segment.at.y > segment.to.y:
var segment = segment
swap(segment.at, segment.to) swap(segment.at, segment.to)
sortedSegments.add((segment, -1.int16)) winding = -1
if ceil(segment.to.y).int < image.height div 2:
topHalf.add((segment, winding))
elif segment.at.y.int >= image.height div 2:
bottomHalf.add((segment, winding))
else: else:
sortedSegments.add((segment, 1.int16)) fullHeight.add((segment, winding))
# 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(sortedSegments) bounds = computeBounds(topHalf, bottomHalf, fullHeight)
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)
@ -800,30 +807,44 @@ proc fillShapes(
initialOffset = offset / 2 initialOffset = offset / 2
var var
hits = newSeq[(float32, int16)](4)
coverages = newSeq[uint8](image.width) coverages = newSeq[uint8](image.width)
hits = newSeq[(float32, int16)](4)
numHits: int numHits: int
for y in startY ..< stopY: for y in startY ..< stopY:
# Reset buffer for this row # Reset buffer for this row
zeroMem(coverages[0].addr, coverages.len) zeroMem(coverages[0].addr, coverages.len)
# Do scanlines for this row proc intersects(
for m in 0 ..< quality: scanline: Line,
let segment: Segment,
yLine = y.float32 + initialOffset + offset * m.float32 + ep winding: int16,
scanline = Line(a: vec2(0, yLine), b: vec2(1000, yLine)) hits: var seq[(float32, int16)],
numHits = 0 numHits: var int
for (segment, winding) in sortedSegments: ) {.inline.} =
if segment.at.y > yLine or segment.to.y < y.float32: if segment.at.y <= scanline.a.y and segment.to.y >= scanline.a.y:
continue
var at: Vec2 var at: Vec2
if scanline.intersects(segment, at):# and segment.to != at: if scanline.intersects(segment, at):# and segment.to != at:
if numHits == hits.len: if numHits == hits.len:
hits.setLen(hits.len * 2) hits.setLen(hits.len * 2)
hits[numHits] = (at.x.clamp(0, image.width.float32), winding) hits[numHits] = (at.x.clamp(0, scanline.b.x), winding)
inc numHits inc numHits
# Do scanlines for this row
for m in 0 ..< quality:
let
yLine = y.float32 + initialOffset + offset * m.float32 + ep
scanline = Line(a: vec2(0, yLine), b: vec2(image.width.float32, yLine))
numHits = 0
if y < image.height div 2:
for (segment, winding) in topHalf:
scanline.intersects(segment, winding, hits, numHits)
else:
for (segment, winding) in bottomHalf:
scanline.intersects(segment, winding, hits, numHits)
for (segment, winding) in fullHeight:
scanline.intersects(segment, winding, hits, numHits)
quickSort(hits, 0, numHits - 1) quickSort(hits, 0, numHits - 1)
proc shouldFill(windingRule: WindingRule, count: int): bool {.inline.} = proc shouldFill(windingRule: WindingRule, count: int): bool {.inline.} =
@ -841,7 +862,7 @@ proc fillShapes(
var var
fillStart = x.int fillStart = x.int
leftCover = if at.int - x.int > 0: ceil(x) - x else: at - x leftCover = if at.int - x.int > 0: trunc(x) + 1 - x else: at - x
if leftCover != 0: if leftCover != 0:
inc fillStart inc fillStart
if shouldFill(windingRule, count): if shouldFill(windingRule, count):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

After

Width:  |  Height:  |  Size: 356 KiB