commit
77701751c3
3 changed files with 84 additions and 57 deletions
|
@ -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)
|
||||||
|
|
|
@ -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 |
Loading…
Reference in a new issue