|
@ -1,4 +1,4 @@
|
||||||
version = "0.0.18"
|
version = "0.0.19"
|
||||||
author = "Andre von Houck and Ryan Oldenburg"
|
author = "Andre von Houck and Ryan Oldenburg"
|
||||||
description = "Full-featured 2d graphics library for Nim."
|
description = "Full-featured 2d graphics library for Nim."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
|
@ -335,6 +335,16 @@ proc blendNormal*(backdrop, source: ColorRGBA): ColorRGBA =
|
||||||
result = source
|
result = source
|
||||||
result = alphaFix(backdrop, source, result)
|
result = alphaFix(backdrop, source, result)
|
||||||
|
|
||||||
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
|
proc blendNormalSimd*(backdrop, source: M128i): M128i =
|
||||||
|
let
|
||||||
|
backdrops = cast[array[4, ColorRGBA]](backdrop)
|
||||||
|
sources = cast[array[4, ColorRGBA]](source)
|
||||||
|
var blended: array[4, ColorRGBA]
|
||||||
|
for i in 0 ..< 4:
|
||||||
|
blended[i] = blendNormal(backdrops[i], sources[i])
|
||||||
|
cast[M128i](blended)
|
||||||
|
|
||||||
proc blendDarken(backdrop, source: ColorRGBA): ColorRGBA =
|
proc blendDarken(backdrop, source: ColorRGBA): ColorRGBA =
|
||||||
result.r = min(backdrop.r, source.r)
|
result.r = min(backdrop.r, source.r)
|
||||||
result.g = min(backdrop.g, source.g)
|
result.g = min(backdrop.g, source.g)
|
||||||
|
|
|
@ -24,7 +24,7 @@ proc toPremultipliedAlpha*(c: ColorRGBA): ColorRGBA {.inline.} =
|
||||||
result.a = c.a
|
result.a = c.a
|
||||||
|
|
||||||
proc toStraightAlpha*(c: ColorRGBA): ColorRGBA {.inline.} =
|
proc toStraightAlpha*(c: ColorRGBA): ColorRGBA {.inline.} =
|
||||||
## Converts a color to from premultiplied alpha to straight alpha.
|
## Converts a color from premultiplied alpha to straight alpha.
|
||||||
result = c
|
result = c
|
||||||
if result.a != 0 and result.a != 255:
|
if result.a != 0 and result.a != 255:
|
||||||
let multiplier = ((255 / c.a.float32) * 255).uint32
|
let multiplier = ((255 / c.a.float32) * 255).uint32
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
import vmath, images, chroma, strutils, algorithm, common, bumpy, blends
|
import vmath, images, chroma, strutils, common, bumpy, blends, common
|
||||||
|
|
||||||
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
|
import nimsimd/sse2
|
||||||
|
|
||||||
type
|
type
|
||||||
WindingRule* = enum
|
WindingRule* = enum
|
||||||
|
@ -28,11 +31,10 @@ when defined(release):
|
||||||
proc parameterCount(kind: PathCommandKind): int =
|
proc parameterCount(kind: PathCommandKind): int =
|
||||||
case kind:
|
case kind:
|
||||||
of Close: 0
|
of Close: 0
|
||||||
of Move, Line, RMove, RLine: 2
|
of Move, Line, RMove, RLine, TQuad, RTQuad: 2
|
||||||
of HLine, VLine, RHLine, RVLine: 1
|
of HLine, VLine, RHLine, RVLine: 1
|
||||||
of Cubic, RCubic: 6
|
of Cubic, RCubic: 6
|
||||||
of SCubic, RSCubic, Quad, RQuad: 4
|
of SCubic, RSCubic, Quad, RQuad: 4
|
||||||
of TQuad, RTQuad: 2
|
|
||||||
of Arc, RArc: 7
|
of Arc, RArc: 7
|
||||||
|
|
||||||
proc parsePath*(path: string): Path =
|
proc parsePath*(path: string): Path =
|
||||||
|
@ -368,7 +370,7 @@ proc polygon*(path: var Path, x, y, size: float32, sides: int) =
|
||||||
)
|
)
|
||||||
|
|
||||||
proc commandsToShapes*(path: Path): seq[seq[Segment]] =
|
proc commandsToShapes*(path: Path): seq[seq[Segment]] =
|
||||||
## Converts SVG-like commands to simpler polygon
|
## Converts SVG-like commands to line segments.
|
||||||
|
|
||||||
var
|
var
|
||||||
start, at: Vec2
|
start, at: Vec2
|
||||||
|
@ -709,6 +711,264 @@ proc commandsToShapes*(path: Path): seq[seq[Segment]] =
|
||||||
if shape.len > 0:
|
if shape.len > 0:
|
||||||
result.add(shape)
|
result.add(shape)
|
||||||
|
|
||||||
|
proc quickSort(a: var seq[(float32, bool)], inl, inr: int) =
|
||||||
|
var
|
||||||
|
r = inr
|
||||||
|
l = inl
|
||||||
|
let n = r - l + 1
|
||||||
|
if n < 2:
|
||||||
|
return
|
||||||
|
let p = a[l + 3 * n div 4][0]
|
||||||
|
while l <= r:
|
||||||
|
if a[l][0] < p:
|
||||||
|
inc l
|
||||||
|
elif a[r][0] > p:
|
||||||
|
dec r
|
||||||
|
else:
|
||||||
|
swap(a[l], a[r])
|
||||||
|
inc l
|
||||||
|
dec r
|
||||||
|
quickSort(a, inl, r)
|
||||||
|
quickSort(a, l, inr)
|
||||||
|
|
||||||
|
proc computeBounds(shape: seq[Segment]): Rect =
|
||||||
|
var
|
||||||
|
xMin = float32.high
|
||||||
|
xMax = float32.low
|
||||||
|
yMin = float32.high
|
||||||
|
yMax = float32.low
|
||||||
|
for segment in shape:
|
||||||
|
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 fillShapes(
|
||||||
|
image: Image,
|
||||||
|
shapes: seq[seq[Segment]],
|
||||||
|
color: ColorRGBA,
|
||||||
|
windingRule: WindingRule
|
||||||
|
) =
|
||||||
|
var sortedShapes = newSeq[seq[(Segment, bool)]](shapes.len)
|
||||||
|
for i, sorted in sortedShapes.mpairs:
|
||||||
|
for j, segment in shapes[i]:
|
||||||
|
if segment.at.y == segment.to.y or segment.at - segment.to == Vec2():
|
||||||
|
# Skip horizontal and zero-length
|
||||||
|
continue
|
||||||
|
var
|
||||||
|
segment = segment
|
||||||
|
winding = segment.at.y > segment.to.y
|
||||||
|
if winding:
|
||||||
|
swap(segment.at, segment.to)
|
||||||
|
sorted.add((segment, winding))
|
||||||
|
|
||||||
|
# Compute the bounds of each shape
|
||||||
|
var bounds = newSeq[Rect](shapes.len)
|
||||||
|
for i, shape in shapes:
|
||||||
|
bounds[i] = computeBounds(shape)
|
||||||
|
|
||||||
|
# Figure out the total bounds of all the shapes
|
||||||
|
var
|
||||||
|
minX = float32.high
|
||||||
|
minY = float32.high
|
||||||
|
maxY = float32.low
|
||||||
|
for bounds in bounds:
|
||||||
|
minX = min(minX, bounds.x)
|
||||||
|
minY = min(minY, bounds.y)
|
||||||
|
maxY = max(maxY, bounds.y + bounds.h)
|
||||||
|
|
||||||
|
# Rasterize only within the total bounds
|
||||||
|
let
|
||||||
|
startX = max(0, minX.int)
|
||||||
|
startY = max(0, miny.int)
|
||||||
|
stopY = min(image.height, maxY.int)
|
||||||
|
|
||||||
|
const
|
||||||
|
quality = 5 # Must divide 255 cleanly
|
||||||
|
sampleCoverage = 255.uint8 div quality
|
||||||
|
ep = 0.0001 * PI
|
||||||
|
offset = 1 / quality.float32
|
||||||
|
initialOffset = offset / 2
|
||||||
|
|
||||||
|
var
|
||||||
|
hits = newSeq[(float32, bool)](4)
|
||||||
|
coverages = newSeq[uint32](image.width)
|
||||||
|
numHits: int
|
||||||
|
|
||||||
|
for y in startY ..< stopY:
|
||||||
|
# Reset buffer for this row
|
||||||
|
zeroMem(coverages[0].addr, coverages.len * 4)
|
||||||
|
|
||||||
|
# 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(1000, yLine))
|
||||||
|
numHits = 0
|
||||||
|
for i, shape in sortedShapes:
|
||||||
|
let bounds = bounds[i]
|
||||||
|
if bounds.y > y.float32 or bounds.y + bounds.h < y.float32:
|
||||||
|
continue
|
||||||
|
for (segment, winding) in shape:
|
||||||
|
var at: Vec2
|
||||||
|
if scanline.intersects(segment, at):# and segment.to != at:
|
||||||
|
if numHits == hits.len:
|
||||||
|
hits.setLen(hits.len * 2)
|
||||||
|
hits[numHits] = (at.x.clamp(0, image.width.float32), winding)
|
||||||
|
inc numHits
|
||||||
|
|
||||||
|
quickSort(hits, 0, numHits - 1)
|
||||||
|
|
||||||
|
proc shouldFill(windingRule: WindingRule, count: int): bool {.inline.} =
|
||||||
|
case windingRule:
|
||||||
|
of wrNonZero:
|
||||||
|
count != 0
|
||||||
|
of wrEvenOdd:
|
||||||
|
count mod 2 != 0
|
||||||
|
|
||||||
|
var
|
||||||
|
x: float32
|
||||||
|
count: int
|
||||||
|
for i in 0 ..< numHits:
|
||||||
|
let (at, winding) = hits[i]
|
||||||
|
|
||||||
|
var
|
||||||
|
fillStart = x.int
|
||||||
|
leftCover = if at.int - x.int > 0: ceil(x) - x else: at - x
|
||||||
|
if leftCover != 0:
|
||||||
|
inc fillStart
|
||||||
|
if shouldFill(windingRule, count):
|
||||||
|
coverages[x.int] += (leftCover * sampleCoverage.float32).uint8
|
||||||
|
|
||||||
|
if at.int - x.int > 0:
|
||||||
|
let rightCover = at - trunc(at)
|
||||||
|
if rightCover > 0 and shouldFill(windingRule, count):
|
||||||
|
coverages[at.int] += (rightCover * sampleCoverage.float32).uint8
|
||||||
|
|
||||||
|
let fillLen = at.int - fillStart
|
||||||
|
if fillLen > 0 and shouldFill(windingRule, count):
|
||||||
|
var i = fillStart
|
||||||
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
|
let m = mm_set1_epi32(sampleCoverage.int32)
|
||||||
|
for j in countup(i, fillStart + fillLen - 4, 4):
|
||||||
|
let current = mm_loadu_si128(coverages[j].addr)
|
||||||
|
mm_storeu_si128(coverages[j].addr, mm_add_epi32(m, current))
|
||||||
|
i += 4
|
||||||
|
for j in i ..< fillStart + fillLen:
|
||||||
|
coverages[j] += sampleCoverage
|
||||||
|
|
||||||
|
count += (if winding: -1 else: 1)
|
||||||
|
x = at
|
||||||
|
|
||||||
|
# Apply the coverage and blend
|
||||||
|
var x = startX
|
||||||
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
|
# When supported, SIMD blend as much as possible
|
||||||
|
|
||||||
|
let
|
||||||
|
alphaMask = mm_set1_epi32(cast[int32](0xff000000))
|
||||||
|
colorMask = mm_set1_epi32(cast[int32](0x00ffffff))
|
||||||
|
div255 = mm_set1_epi16(cast[int16](0x8081))
|
||||||
|
zero = mm_set1_epi32(0)
|
||||||
|
v255 = mm_set1_epi32(255)
|
||||||
|
|
||||||
|
for _ in countup(x, coverages.len - 4, 4):
|
||||||
|
var coverage = mm_loadu_si128(coverages[x].addr)
|
||||||
|
|
||||||
|
if mm_movemask_epi8(mm_cmpeq_epi32(coverage, zero)) != 0xffff:
|
||||||
|
# If the coverages are not all zero
|
||||||
|
var source = mm_set1_epi32(cast[int32](color))
|
||||||
|
|
||||||
|
if mm_movemask_epi8(mm_cmpeq_epi32(coverage, v255)) != 0xffff:
|
||||||
|
# If the coverages are not all 255
|
||||||
|
|
||||||
|
# Shift the coverages to `a` for multiplying
|
||||||
|
coverage = mm_slli_epi32(coverage, 24)
|
||||||
|
|
||||||
|
var alpha = mm_and_si128(source, alphaMask)
|
||||||
|
alpha = mm_mulhi_epu16(alpha, coverage)
|
||||||
|
alpha = mm_srli_epi16(mm_mulhi_epu16(alpha, div255), 7)
|
||||||
|
alpha = mm_slli_epi32(alpha, 8)
|
||||||
|
|
||||||
|
source = mm_or_si128(mm_and_si128(source, colorMask), alpha)
|
||||||
|
|
||||||
|
let
|
||||||
|
index = image.dataIndex(x, y)
|
||||||
|
backdrop = mm_loadu_si128(image.data[index].addr)
|
||||||
|
mm_storeu_si128(image.data[index].addr, blendNormalSimd(backdrop, source))
|
||||||
|
|
||||||
|
x += 4
|
||||||
|
break
|
||||||
|
|
||||||
|
while x < image.width:
|
||||||
|
if x + 2 <= coverages.len:
|
||||||
|
let peeked = cast[ptr uint64](coverages[x].addr)[]
|
||||||
|
if peeked == 0:
|
||||||
|
x += 2
|
||||||
|
continue
|
||||||
|
|
||||||
|
let coverage = coverages[x]
|
||||||
|
if coverage != 0:
|
||||||
|
var source = color
|
||||||
|
if coverage != 255:
|
||||||
|
source.a = ((color.a.uint16 * coverage) div 255).uint8
|
||||||
|
|
||||||
|
let backdrop = image.getRgbaUnsafe(x, y)
|
||||||
|
image.setRgbaUnsafe(x, y, blendNormal(backdrop, source))
|
||||||
|
inc x
|
||||||
|
|
||||||
|
proc parseSomePath(path: SomePath): seq[seq[Segment]] =
|
||||||
|
when type(path) is string:
|
||||||
|
parsePath(path).commandsToShapes()
|
||||||
|
elif type(path) is Path:
|
||||||
|
path.commandsToShapes()
|
||||||
|
elif type(path) is seq[seq[Segment]]:
|
||||||
|
path
|
||||||
|
|
||||||
|
proc fillPath*(
|
||||||
|
image: Image,
|
||||||
|
path: SomePath,
|
||||||
|
color: ColorRGBA,
|
||||||
|
windingRule = wrNonZero
|
||||||
|
) {.inline.} =
|
||||||
|
image.fillShapes(parseSomePath(path), color, windingRule)
|
||||||
|
|
||||||
|
proc fillPath*(
|
||||||
|
image: Image,
|
||||||
|
path: SomePath,
|
||||||
|
color: ColorRGBA,
|
||||||
|
pos: Vec2,
|
||||||
|
windingRule = wrNonZero
|
||||||
|
) =
|
||||||
|
var shapes = parseSomePath(path)
|
||||||
|
for shape in shapes.mitems:
|
||||||
|
for segment in shape.mitems:
|
||||||
|
segment += pos
|
||||||
|
image.fillShapes(shapes, color, windingRule)
|
||||||
|
|
||||||
|
proc fillPath*(
|
||||||
|
image: Image,
|
||||||
|
path: SomePath,
|
||||||
|
color: ColorRGBA,
|
||||||
|
mat: Mat3,
|
||||||
|
windingRule = wrNonZero
|
||||||
|
) =
|
||||||
|
var shapes = parseSomePath(path)
|
||||||
|
for shape in shapes.mitems:
|
||||||
|
for segment in shape.mitems:
|
||||||
|
segment = mat * segment
|
||||||
|
image.fillShapes(shapes, color, windingRule)
|
||||||
|
|
||||||
proc strokeShapes(
|
proc strokeShapes(
|
||||||
shapes: seq[seq[Segment]],
|
shapes: seq[seq[Segment]],
|
||||||
color: ColorRGBA,
|
color: ColorRGBA,
|
||||||
|
@ -756,197 +1016,14 @@ proc strokeShapes(
|
||||||
if strokeShape.len > 0:
|
if strokeShape.len > 0:
|
||||||
result.add(strokeShape)
|
result.add(strokeShape)
|
||||||
|
|
||||||
proc computeBounds(shape: seq[Segment]): Rect =
|
|
||||||
var
|
|
||||||
xMin = float32.high
|
|
||||||
xMax = float32.low
|
|
||||||
yMin = float32.high
|
|
||||||
yMax = float32.low
|
|
||||||
for segment in shape:
|
|
||||||
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 fillShapes*(
|
|
||||||
image: Image,
|
|
||||||
shapes: seq[seq[Segment]],
|
|
||||||
color: ColorRGBA,
|
|
||||||
windingRule: WindingRule
|
|
||||||
) =
|
|
||||||
var sortedShapes = newSeq[seq[(Segment, bool)]](shapes.len)
|
|
||||||
for i, sorted in sortedShapes.mpairs:
|
|
||||||
for j, segment in shapes[i]:
|
|
||||||
if segment.at.y == segment.to.y or segment.at - segment.to == Vec2():
|
|
||||||
# Skip horizontal and zero-length
|
|
||||||
continue
|
|
||||||
var
|
|
||||||
segment = segment
|
|
||||||
winding = segment.at.y > segment.to.y
|
|
||||||
if winding:
|
|
||||||
swap(segment.at, segment.to)
|
|
||||||
sorted.add((segment, winding))
|
|
||||||
|
|
||||||
# Compute the bounds of each shape
|
|
||||||
var bounds = newSeq[Rect](shapes.len)
|
|
||||||
for i, shape in shapes:
|
|
||||||
bounds[i] = computeBounds(shape)
|
|
||||||
|
|
||||||
# Figure out the total bounds of all the shapes
|
|
||||||
var
|
|
||||||
minX = float32.high
|
|
||||||
minY = float32.high
|
|
||||||
maxY = float32.low
|
|
||||||
for bounds in bounds:
|
|
||||||
minX = min(minX, bounds.x)
|
|
||||||
minY = min(minY, bounds.y)
|
|
||||||
maxY = max(maxY, bounds.y + bounds.h)
|
|
||||||
|
|
||||||
# Rasterize only within the total bounds
|
|
||||||
let
|
|
||||||
startX = max(0, minX.int)
|
|
||||||
startY = max(0, miny.int)
|
|
||||||
stopY = min(image.height, maxY.int)
|
|
||||||
|
|
||||||
const
|
|
||||||
quality = 4
|
|
||||||
ep = 0.0001 * PI
|
|
||||||
|
|
||||||
proc scanLineHits(
|
|
||||||
shapes: seq[seq[(Segment, bool)]],
|
|
||||||
bounds: seq[Rect],
|
|
||||||
hits: var seq[(float32, bool)],
|
|
||||||
size: Vec2,
|
|
||||||
y: int,
|
|
||||||
shiftY: float32
|
|
||||||
) {.inline.} =
|
|
||||||
hits.setLen(0)
|
|
||||||
|
|
||||||
let
|
|
||||||
yLine = y.float32 + ep + shiftY
|
|
||||||
scanline = Line(a: vec2(-10000, yLine), b: vec2(10000, yLine))
|
|
||||||
|
|
||||||
for i, shape in shapes:
|
|
||||||
let bounds = bounds[i]
|
|
||||||
if bounds.y > y.float32 or bounds.y + bounds.h < y.float32:
|
|
||||||
continue
|
|
||||||
for (segment, winding) in shape:
|
|
||||||
# Lines often connect and we need them to not share starts and ends
|
|
||||||
var at: Vec2
|
|
||||||
if scanline.intersects(segment, at) and segment.to != at:
|
|
||||||
hits.add((at.x.clamp(0, size.x), winding))
|
|
||||||
|
|
||||||
hits.sort(proc(a, b: (float32, bool)): int = cmp(a[0], b[0]))
|
|
||||||
|
|
||||||
var
|
|
||||||
hits = newSeq[(float32, bool)]()
|
|
||||||
alphas = newSeq[float32](image.width)
|
|
||||||
for y in startY ..< stopY:
|
|
||||||
# Reset alphas for this row.
|
|
||||||
zeroMem(alphas[0].addr, alphas.len * 4)
|
|
||||||
|
|
||||||
# Do scan lines for this row.
|
|
||||||
for m in 0 ..< quality:
|
|
||||||
sortedShapes.scanLineHits(bounds, hits, image.wh, y, float32(m) / float32(quality))
|
|
||||||
if hits.len == 0:
|
|
||||||
continue
|
|
||||||
var
|
|
||||||
penFill = 0
|
|
||||||
curHit = 0
|
|
||||||
for x in startX ..< image.width:
|
|
||||||
var penEdge: float32
|
|
||||||
case windingRule
|
|
||||||
of wrNonZero:
|
|
||||||
penEdge = penFill.float32
|
|
||||||
of wrEvenOdd:
|
|
||||||
if penFill mod 2 == 0:
|
|
||||||
penEdge = 0.0
|
|
||||||
else:
|
|
||||||
penEdge = 1.0
|
|
||||||
|
|
||||||
while true:
|
|
||||||
if curHit >= hits.len or x != hits[curHit][0].int:
|
|
||||||
break
|
|
||||||
let
|
|
||||||
cover = hits[curHit][0] - x.float32
|
|
||||||
winding = hits[curHit][1]
|
|
||||||
if winding == false:
|
|
||||||
penFill += 1
|
|
||||||
penEdge += 1.0 - cover
|
|
||||||
else:
|
|
||||||
penFill -= 1
|
|
||||||
penEdge -= 1.0 - cover
|
|
||||||
inc curHit
|
|
||||||
alphas[x] += penEdge
|
|
||||||
|
|
||||||
for x in 0 ..< image.width:
|
|
||||||
let a = clamp(abs(alphas[x]) / float32(quality), 0.0, 1.0)
|
|
||||||
if a > 0:
|
|
||||||
var colorWithAlpha = color
|
|
||||||
colorWithAlpha.a = uint8(a * 255.0)
|
|
||||||
let rgba = image.getRgbaUnsafe(x, y)
|
|
||||||
image.setRgbaUnsafe(x, y, blendNormal(rgba, colorWithAlpha))
|
|
||||||
|
|
||||||
proc parseSomePath(path: SomePath): seq[seq[Segment]] =
|
|
||||||
when type(path) is string:
|
|
||||||
parsePath(path).commandsToShapes()
|
|
||||||
elif type(path) is Path:
|
|
||||||
path.commandsToShapes()
|
|
||||||
elif type(path) is seq[seq[Segment]]:
|
|
||||||
path
|
|
||||||
|
|
||||||
proc fillPath*(
|
|
||||||
image: Image,
|
|
||||||
path: SomePath,
|
|
||||||
color: ColorRGBA,
|
|
||||||
windingRule = wrNonZero
|
|
||||||
) {.inline.} =
|
|
||||||
image.fillShapes(parseSomePath(path), color, windingRule)
|
|
||||||
|
|
||||||
proc fillPath*(
|
|
||||||
image: Image,
|
|
||||||
path: SomePath,
|
|
||||||
color: ColorRGBA,
|
|
||||||
pos: Vec2,
|
|
||||||
windingRule = wrNonZero
|
|
||||||
) =
|
|
||||||
var shapes = parseSomePath(path)
|
|
||||||
for shape in shapes.mitems:
|
|
||||||
for segment in shape.mitems:
|
|
||||||
segment += pos
|
|
||||||
image.fillShapes(shapes, color, windingRule)
|
|
||||||
|
|
||||||
proc fillPath*(
|
|
||||||
image: Image,
|
|
||||||
path: SomePath,
|
|
||||||
color: ColorRGBA,
|
|
||||||
mat: Mat3,
|
|
||||||
windingRule = wrNonZero
|
|
||||||
) =
|
|
||||||
var shapes = parseSomePath(path)
|
|
||||||
for shape in shapes.mitems:
|
|
||||||
for segment in shape.mitems:
|
|
||||||
segment = mat * segment
|
|
||||||
image.fillShapes(shapes, color, windingRule)
|
|
||||||
|
|
||||||
proc strokePath*(
|
proc strokePath*(
|
||||||
image: Image,
|
image: Image,
|
||||||
path: SomePath,
|
path: SomePath,
|
||||||
color: ColorRGBA,
|
color: ColorRGBA,
|
||||||
strokeWidth: float32,
|
strokeWidth = 1.0,
|
||||||
windingRule = wrNonZero
|
windingRule = wrNonZero
|
||||||
) =
|
) =
|
||||||
var strokeShapes = strokeShapes(
|
let strokeShapes = strokeShapes(
|
||||||
parseSomePath(path),
|
parseSomePath(path),
|
||||||
color,
|
color,
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
|
@ -958,7 +1035,7 @@ proc strokePath*(
|
||||||
image: Image,
|
image: Image,
|
||||||
path: SomePath,
|
path: SomePath,
|
||||||
color: ColorRGBA,
|
color: ColorRGBA,
|
||||||
strokeWidth: float32,
|
strokeWidth = 1.0,
|
||||||
pos: Vec2,
|
pos: Vec2,
|
||||||
windingRule = wrNonZero
|
windingRule = wrNonZero
|
||||||
) =
|
) =
|
||||||
|
@ -977,7 +1054,7 @@ proc strokePath*(
|
||||||
image: Image,
|
image: Image,
|
||||||
path: SomePath,
|
path: SomePath,
|
||||||
color: ColorRGBA,
|
color: ColorRGBA,
|
||||||
strokeWidth: float32,
|
strokeWidth = 1.0,
|
||||||
mat: Mat3,
|
mat: Mat3,
|
||||||
windingRule = wrNonZero
|
windingRule = wrNonZero
|
||||||
) =
|
) =
|
||||||
|
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 976 B After Width: | Height: | Size: 959 B |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 981 B After Width: | Height: | Size: 974 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 832 B After Width: | Height: | Size: 813 B |
Before Width: | Height: | Size: 714 B After Width: | Height: | Size: 701 B |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 347 KiB After Width: | Height: | Size: 351 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 4.1 KiB |