masks + paths
This commit is contained in:
parent
5afd784b20
commit
369066e04d
7 changed files with 408 additions and 102 deletions
|
@ -1,8 +1,8 @@
|
||||||
import pixie/images, pixie/paths, pixie/common, pixie/blends,
|
import pixie/images, pixie/masks, pixie/paths, pixie/common, pixie/blends,
|
||||||
pixie/fileformats/bmp, pixie/fileformats/png, pixie/fileformats/jpg,
|
pixie/fileformats/bmp, pixie/fileformats/png, pixie/fileformats/jpg,
|
||||||
pixie/fileformats/svg, flatty/binny, os
|
pixie/fileformats/svg, flatty/binny, os
|
||||||
|
|
||||||
export images, paths, common, blends
|
export images, masks, paths, common, blends
|
||||||
|
|
||||||
type
|
type
|
||||||
FileFormat* = enum
|
FileFormat* = enum
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import chroma, pixie/common, math, zippy, zippy/crc, flatty/binny, pixie/images
|
import chroma, pixie/common, math, zippy, zippy/crc, flatty/binny,
|
||||||
|
pixie/images, pixie/masks
|
||||||
|
|
||||||
# See http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html
|
# See http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html
|
||||||
|
|
||||||
|
@ -501,5 +502,16 @@ proc encodePng*(image: Image): string =
|
||||||
image.width, image.height, 4, image.data[0].addr, image.data.len * 4
|
image.width, image.height, 4, image.data[0].addr, image.data.len * 4
|
||||||
))
|
))
|
||||||
|
|
||||||
|
proc encodePng*(mask: Mask): string =
|
||||||
|
## Encodes the mask data into the PNG file format.
|
||||||
|
if mask.data.len == 0:
|
||||||
|
raise newException(
|
||||||
|
PixieError,
|
||||||
|
"Mask has no data (are height and width 0?)"
|
||||||
|
)
|
||||||
|
cast[string](encodePng(
|
||||||
|
mask.width, mask.height, 1, mask.data[0].addr, mask.data.len
|
||||||
|
))
|
||||||
|
|
||||||
when defined(release):
|
when defined(release):
|
||||||
{.pop.}
|
{.pop.}
|
||||||
|
|
67
src/pixie/masks.nim
Normal file
67
src/pixie/masks.nim
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import common, vmath
|
||||||
|
|
||||||
|
type
|
||||||
|
Mask* = ref object
|
||||||
|
## Mask object that holds mask opacity data.
|
||||||
|
width*, height*: int
|
||||||
|
data*: seq[uint8]
|
||||||
|
|
||||||
|
when defined(release):
|
||||||
|
{.push checks: off.}
|
||||||
|
|
||||||
|
proc newMask*(width, height: int): Mask =
|
||||||
|
## Creates a new mask with the parameter dimensions.
|
||||||
|
if width <= 0 or height <= 0:
|
||||||
|
raise newException(PixieError, "Mask width and height must be > 0")
|
||||||
|
|
||||||
|
result = Mask()
|
||||||
|
result.width = width
|
||||||
|
result.height = height
|
||||||
|
result.data = newSeq[uint8](width * height)
|
||||||
|
|
||||||
|
proc wh*(mask: Mask): Vec2 {.inline.} =
|
||||||
|
## Return with and height as a size vector.
|
||||||
|
vec2(mask.width.float32, mask.height.float32)
|
||||||
|
|
||||||
|
proc copy*(mask: Mask): Mask =
|
||||||
|
## Copies the image data into a new image.
|
||||||
|
result = newMask(mask.width, mask.height)
|
||||||
|
result.data = mask.data
|
||||||
|
|
||||||
|
proc `$`*(mask: Mask): string =
|
||||||
|
## Prints the mask size.
|
||||||
|
"<Mask " & $mask.width & "x" & $mask.height & ">"
|
||||||
|
|
||||||
|
proc inside*(mask: Mask, x, y: int): bool {.inline.} =
|
||||||
|
## Returns true if (x, y) is inside the mask.
|
||||||
|
x >= 0 and x < mask.width and y >= 0 and y < mask.height
|
||||||
|
|
||||||
|
proc dataIndex*(mask: Mask, x, y: int): int {.inline.} =
|
||||||
|
mask.width * y + x
|
||||||
|
|
||||||
|
proc getValueUnsafe*(mask: Mask, x, y: int): uint8 {.inline.} =
|
||||||
|
## Gets a color from (x, y) coordinates.
|
||||||
|
## * No bounds checking *
|
||||||
|
## Make sure that x, y are in bounds.
|
||||||
|
## Failure in the assumptions will case unsafe memory reads.
|
||||||
|
result = mask.data[mask.width * y + x]
|
||||||
|
|
||||||
|
proc `[]`*(mask: Mask, x, y: int): uint8 {.inline.} =
|
||||||
|
## Gets a pixel at (x, y) or returns transparent black if outside of bounds.
|
||||||
|
if mask.inside(x, y):
|
||||||
|
return mask.getValueUnsafe(x, y)
|
||||||
|
|
||||||
|
proc setValueUnsafe*(mask: Mask, x, y: int, value: uint8) {.inline.} =
|
||||||
|
## Sets a value from (x, y) coordinates.
|
||||||
|
## * No bounds checking *
|
||||||
|
## Make sure that x, y are in bounds.
|
||||||
|
## Failure in the assumptions will case unsafe memory writes.
|
||||||
|
mask.data[mask.dataIndex(x, y)] = value
|
||||||
|
|
||||||
|
proc `[]=`*(mask: Mask, x, y: int, value: uint8) {.inline.} =
|
||||||
|
## Sets a pixel at (x, y) or does nothing if outside of bounds.
|
||||||
|
if mask.inside(x, y):
|
||||||
|
mask.setValueUnsafe(x, y, value)
|
||||||
|
|
||||||
|
when defined(release):
|
||||||
|
{.pop.}
|
|
@ -1,4 +1,4 @@
|
||||||
import common, strutils, vmath, images, chroma, bumpy, blends
|
import common, strutils, vmath, images, masks, chroma, bumpy, blends
|
||||||
|
|
||||||
when defined(amd64) and not defined(pixieNoSimd):
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
import nimsimd/sse2
|
import nimsimd/sse2
|
||||||
|
@ -767,13 +767,18 @@ proc computeBounds(seqs: varargs[seq[(Segment, int16)]]): Rect =
|
||||||
result.w = xMax - xMin
|
result.w = xMax - xMin
|
||||||
result.h = yMax - yMin
|
result.h = yMax - yMin
|
||||||
|
|
||||||
proc fillShapes(
|
proc shouldFill(windingRule: WindingRule, count: int): bool {.inline.} =
|
||||||
image: Image,
|
case windingRule:
|
||||||
shapes: seq[seq[Vec2]],
|
of wrNonZero:
|
||||||
color: ColorRGBA,
|
count != 0
|
||||||
windingRule: WindingRule
|
of wrEvenOdd:
|
||||||
) =
|
count mod 2 != 0
|
||||||
var topHalf, bottomHalf, fullHeight: seq[(Segment, int16)]
|
|
||||||
|
proc partitionSegments(shapes: seq[seq[Vec2]], middle: int): tuple[
|
||||||
|
topHalf: seq[(Segment, int16)],
|
||||||
|
bottomHalf: seq[(Segment, int16)],
|
||||||
|
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
|
||||||
|
@ -784,12 +789,107 @@ proc fillShapes(
|
||||||
if segment.at.y > segment.to.y:
|
if segment.at.y > segment.to.y:
|
||||||
swap(segment.at, segment.to)
|
swap(segment.at, segment.to)
|
||||||
winding = -1
|
winding = -1
|
||||||
if ceil(segment.to.y).int < image.height div 2:
|
if ceil(segment.to.y).int < middle:
|
||||||
topHalf.add((segment, winding))
|
result.topHalf.add((segment, winding))
|
||||||
elif segment.at.y.int >= image.height div 2:
|
elif segment.at.y.int >= middle:
|
||||||
bottomHalf.add((segment, winding))
|
result.bottomHalf.add((segment, winding))
|
||||||
else:
|
else:
|
||||||
fullHeight.add((segment, winding))
|
result.fullHeight.add((segment, winding))
|
||||||
|
|
||||||
|
proc computeCoverages(
|
||||||
|
coverages: var seq[uint8],
|
||||||
|
hits: var seq[(float32, int16)],
|
||||||
|
size: Vec2,
|
||||||
|
y: int,
|
||||||
|
topHalf, bottomHalf, fullHeight: seq[(Segment, int16)],
|
||||||
|
windingRule: WindingRule
|
||||||
|
) =
|
||||||
|
const
|
||||||
|
quality = 5 # Must divide 255 cleanly
|
||||||
|
sampleCoverage = 255.uint8 div quality
|
||||||
|
ep = 0.0001 * PI
|
||||||
|
offset = 1 / quality.float32
|
||||||
|
initialOffset = offset / 2
|
||||||
|
|
||||||
|
proc intersects(
|
||||||
|
scanline: Line,
|
||||||
|
segment: Segment,
|
||||||
|
winding: int16,
|
||||||
|
hits: var seq[(float32, int16)],
|
||||||
|
numHits: var int
|
||||||
|
) {.inline.} =
|
||||||
|
if segment.at.y <= scanline.a.y and segment.to.y >= scanline.a.y:
|
||||||
|
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, scanline.b.x), winding)
|
||||||
|
inc numHits
|
||||||
|
|
||||||
|
var numHits: int
|
||||||
|
|
||||||
|
# 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(size.x, yLine))
|
||||||
|
numHits = 0
|
||||||
|
if y < size.y.int 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)
|
||||||
|
|
||||||
|
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: trunc(x) + 1 - 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 vSampleCoverage = mm_set1_epi8(cast[int8](sampleCoverage))
|
||||||
|
for j in countup(i, fillStart + fillLen - 16, 16):
|
||||||
|
let current = mm_loadu_si128(coverages[j].addr)
|
||||||
|
mm_storeu_si128(
|
||||||
|
coverages[j].addr,
|
||||||
|
mm_add_epi8(current, vSampleCoverage)
|
||||||
|
)
|
||||||
|
i += 16
|
||||||
|
for j in i ..< fillStart + fillLen:
|
||||||
|
coverages[j] += sampleCoverage
|
||||||
|
|
||||||
|
count += winding
|
||||||
|
x = at
|
||||||
|
|
||||||
|
proc fillShapes(
|
||||||
|
image: Image,
|
||||||
|
shapes: seq[seq[Vec2]],
|
||||||
|
color: ColorRGBA,
|
||||||
|
windingRule: WindingRule
|
||||||
|
) =
|
||||||
|
let (topHalf, bottomHalf, fullHeight) =
|
||||||
|
partitionSegments(shapes, image.height div 2)
|
||||||
|
|
||||||
# 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
|
||||||
|
@ -799,105 +899,29 @@ proc fillShapes(
|
||||||
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)
|
||||||
|
|
||||||
const
|
|
||||||
quality = 5 # Must divide 255 cleanly
|
|
||||||
sampleCoverage = 255.uint8 div quality
|
|
||||||
ep = 0.0001 * PI
|
|
||||||
offset = 1 / quality.float32
|
|
||||||
initialOffset = offset / 2
|
|
||||||
|
|
||||||
var
|
var
|
||||||
coverages = newSeq[uint8](image.width)
|
coverages = newSeq[uint8](image.width)
|
||||||
hits = newSeq[(float32, int16)](4)
|
hits = newSeq[(float32, int16)](4)
|
||||||
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)
|
||||||
|
|
||||||
proc intersects(
|
computeCoverages(
|
||||||
scanline: Line,
|
coverages,
|
||||||
segment: Segment,
|
hits,
|
||||||
winding: int16,
|
image.wh,
|
||||||
hits: var seq[(float32, int16)],
|
y,
|
||||||
numHits: var int
|
topHalf, bottomHalf, fullHeight,
|
||||||
) {.inline.} =
|
windingRule
|
||||||
if segment.at.y <= scanline.a.y and segment.to.y >= scanline.a.y:
|
)
|
||||||
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, scanline.b.x), winding)
|
|
||||||
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)
|
|
||||||
|
|
||||||
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: trunc(x) + 1 - 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 vSampleCoverage = mm_set1_epi8(cast[int8](sampleCoverage))
|
|
||||||
for j in countup(i, fillStart + fillLen - 16, 16):
|
|
||||||
let current = mm_loadu_si128(coverages[j].addr)
|
|
||||||
mm_storeu_si128(
|
|
||||||
coverages[j].addr,
|
|
||||||
mm_add_epi8(current, vSampleCoverage)
|
|
||||||
)
|
|
||||||
i += 16
|
|
||||||
for j in i ..< fillStart + fillLen:
|
|
||||||
coverages[j] += sampleCoverage
|
|
||||||
|
|
||||||
count += winding
|
|
||||||
x = at
|
|
||||||
|
|
||||||
# Apply the coverage and blend
|
# Apply the coverage and blend
|
||||||
var x = startX
|
var x = startX
|
||||||
when defined(amd64) and not defined(pixieNoSimd):
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
# When supported, SIMD blend as much as possible
|
# When supported, SIMD blend as much as possible
|
||||||
|
|
||||||
let
|
let
|
||||||
coverageMask1 = cast[M128i]([0xffffffff, 0, 0, 0]) # First 32 bits
|
coverageMask1 = cast[M128i]([uint32.high, 0, 0, 0]) # First 32 bits
|
||||||
coverageMask2 = mm_set1_epi32(cast[int32](0x000000ff)) # Only `r`
|
coverageMask2 = mm_set1_epi32(cast[int32](0x000000ff)) # Only `r`
|
||||||
oddMask = mm_set1_epi16(cast[int16](0xff00))
|
oddMask = mm_set1_epi16(cast[int16](0xff00))
|
||||||
div255 = mm_set1_epi16(cast[int16](0x8081))
|
div255 = mm_set1_epi16(cast[int16](0x8081))
|
||||||
|
@ -982,6 +1006,107 @@ proc fillShapes(
|
||||||
image.setRgbaUnsafe(x, y, blendNormalPremultiplied(backdrop, source))
|
image.setRgbaUnsafe(x, y, blendNormalPremultiplied(backdrop, source))
|
||||||
inc x
|
inc x
|
||||||
|
|
||||||
|
proc fillShapes(
|
||||||
|
mask: Mask,
|
||||||
|
shapes: seq[seq[Vec2]],
|
||||||
|
windingRule: WindingRule
|
||||||
|
) =
|
||||||
|
let (topHalf, bottomHalf, fullHeight) =
|
||||||
|
partitionSegments(shapes, mask.height div 2)
|
||||||
|
|
||||||
|
# Figure out the total bounds of all the shapes,
|
||||||
|
# rasterize only within the total bounds
|
||||||
|
let
|
||||||
|
bounds = computeBounds(topHalf, bottomHalf, fullHeight)
|
||||||
|
startX = max(0, bounds.x.int)
|
||||||
|
startY = max(0, bounds.y.int)
|
||||||
|
stopY = min(mask.height, (bounds.y + bounds.h).int)
|
||||||
|
|
||||||
|
var
|
||||||
|
coverages = newSeq[uint8](mask.width)
|
||||||
|
hits = newSeq[(float32, int16)](4)
|
||||||
|
|
||||||
|
for y in startY ..< stopY:
|
||||||
|
# Reset buffer for this row
|
||||||
|
zeroMem(coverages[0].addr, coverages.len)
|
||||||
|
|
||||||
|
computeCoverages(
|
||||||
|
coverages,
|
||||||
|
hits,
|
||||||
|
mask.wh,
|
||||||
|
y,
|
||||||
|
topHalf, bottomHalf, fullHeight,
|
||||||
|
windingRule
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply the coverage and blend
|
||||||
|
var x = startX
|
||||||
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
|
# When supported, SIMD blend as much as possible
|
||||||
|
let
|
||||||
|
oddMask = mm_set1_epi16(cast[int16](0xff00))
|
||||||
|
v255high = mm_set1_epi16(cast[int16](255.uint16 shl 8))
|
||||||
|
div255 = mm_set1_epi16(cast[int16](0x8081))
|
||||||
|
|
||||||
|
for _ in countup(x, coverages.len - 16, 16):
|
||||||
|
var coverage = mm_loadu_si128(coverages[x].addr)
|
||||||
|
|
||||||
|
let eqZero = mm_cmpeq_epi16(coverage, mm_setzero_si128())
|
||||||
|
if mm_movemask_epi8(eqZero) != 0xffff:
|
||||||
|
# If the coverages are not all zero
|
||||||
|
var
|
||||||
|
coverageEven = mm_slli_epi16(mm_andnot_si128(oddMask, coverage), 8)
|
||||||
|
coverageOdd = mm_and_si128(coverage, oddMask)
|
||||||
|
|
||||||
|
let
|
||||||
|
evenK = mm_sub_epi16(v255high, coverageEven)
|
||||||
|
oddK = mm_sub_epi16(v255high, coverageOdd)
|
||||||
|
|
||||||
|
var
|
||||||
|
backdrop = mm_loadu_si128(mask.data[mask.dataIndex(x, y)].addr)
|
||||||
|
backdropEven = mm_slli_epi16(mm_andnot_si128(oddMask, backdrop), 8)
|
||||||
|
backdropOdd = mm_and_si128(backdrop, oddMask)
|
||||||
|
|
||||||
|
# backdrop * k
|
||||||
|
backdropEven = mm_mulhi_epu16(backdropEven, evenK)
|
||||||
|
backdropOdd = mm_mulhi_epu16(backdropOdd, oddK)
|
||||||
|
|
||||||
|
# div 255
|
||||||
|
backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7)
|
||||||
|
backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7)
|
||||||
|
|
||||||
|
# Shift from high to low bits
|
||||||
|
coverageEven = mm_srli_epi16(coverageEven, 8)
|
||||||
|
coverageOdd = mm_srli_epi16(coverageOdd, 8)
|
||||||
|
|
||||||
|
var
|
||||||
|
blendedEven = mm_add_epi16(coverageEven, backdropEven)
|
||||||
|
blendedOdd = mm_add_epi16(coverageOdd, backdropOdd)
|
||||||
|
|
||||||
|
blendedOdd = mm_slli_epi16(blendedOdd, 8)
|
||||||
|
|
||||||
|
mm_storeu_si128(
|
||||||
|
mask.data[mask.dataIndex(x, y)].addr,
|
||||||
|
mm_or_si128(blendedEven, blendedOdd)
|
||||||
|
)
|
||||||
|
|
||||||
|
x += 16
|
||||||
|
|
||||||
|
while x < mask.width:
|
||||||
|
if x + 8 <= coverages.len:
|
||||||
|
let peeked = cast[ptr uint64](coverages[x].addr)[]
|
||||||
|
if peeked == 0:
|
||||||
|
x += 8
|
||||||
|
continue
|
||||||
|
let coverage = coverages[x]
|
||||||
|
if coverage != 0:
|
||||||
|
let
|
||||||
|
backdrop = mask.getValueUnsafe(x, y)
|
||||||
|
blended =
|
||||||
|
coverage + ((backdrop.uint32 * (255 - coverage)) div 255).uint8
|
||||||
|
mask.setValueUnsafe(x, y, blended)
|
||||||
|
inc x
|
||||||
|
|
||||||
proc strokeShapes(
|
proc strokeShapes(
|
||||||
shapes: seq[seq[Vec2]],
|
shapes: seq[seq[Vec2]],
|
||||||
strokeWidth: float32,
|
strokeWidth: float32,
|
||||||
|
@ -1065,6 +1190,37 @@ proc fillPath*(
|
||||||
segment = mat * segment
|
segment = mat * segment
|
||||||
image.fillShapes(shapes, color, windingRule)
|
image.fillShapes(shapes, color, windingRule)
|
||||||
|
|
||||||
|
proc fillPath*(
|
||||||
|
mask: Mask,
|
||||||
|
path: SomePath,
|
||||||
|
windingRule = wrNonZero
|
||||||
|
) {.inline.} =
|
||||||
|
mask.fillShapes(parseSomePath(path), windingRule)
|
||||||
|
|
||||||
|
proc fillPath*(
|
||||||
|
mask: Mask,
|
||||||
|
path: SomePath,
|
||||||
|
pos: Vec2,
|
||||||
|
windingRule = wrNonZero
|
||||||
|
) =
|
||||||
|
var shapes = parseSomePath(path)
|
||||||
|
for shape in shapes.mitems:
|
||||||
|
for segment in shape.mitems:
|
||||||
|
segment += pos
|
||||||
|
mask.fillShapes(shapes, color, windingRule)
|
||||||
|
|
||||||
|
proc fillPath*(
|
||||||
|
mask: Mask,
|
||||||
|
path: SomePath,
|
||||||
|
mat: Mat3,
|
||||||
|
windingRule = wrNonZero
|
||||||
|
) =
|
||||||
|
var shapes = parseSomePath(path)
|
||||||
|
for shape in shapes.mitems:
|
||||||
|
for segment in shape.mitems:
|
||||||
|
segment = mat * segment
|
||||||
|
mask.fillShapes(shapes, windingRule)
|
||||||
|
|
||||||
proc strokePath*(
|
proc strokePath*(
|
||||||
image: Image,
|
image: Image,
|
||||||
path: SomePath,
|
path: SomePath,
|
||||||
|
@ -1115,5 +1271,52 @@ proc strokePath*(
|
||||||
segment = mat * segment
|
segment = mat * segment
|
||||||
image.fillShapes(strokeShapes, color, windingRule)
|
image.fillShapes(strokeShapes, color, windingRule)
|
||||||
|
|
||||||
|
proc strokePath*(
|
||||||
|
mask: Mask,
|
||||||
|
path: SomePath,
|
||||||
|
strokeWidth = 1.0,
|
||||||
|
windingRule = wrNonZero
|
||||||
|
) =
|
||||||
|
let strokeShapes = strokeShapes(
|
||||||
|
parseSomePath(path),
|
||||||
|
strokeWidth,
|
||||||
|
windingRule
|
||||||
|
)
|
||||||
|
mask.fillShapes(strokeShapes, windingRule)
|
||||||
|
|
||||||
|
proc strokePath*(
|
||||||
|
mask: Mask,
|
||||||
|
path: SomePath,
|
||||||
|
strokeWidth = 1.0,
|
||||||
|
pos: Vec2,
|
||||||
|
windingRule = wrNonZero
|
||||||
|
) =
|
||||||
|
var strokeShapes = strokeShapes(
|
||||||
|
parseSomePath(path),
|
||||||
|
strokeWidth,
|
||||||
|
windingRule
|
||||||
|
)
|
||||||
|
for shape in strokeShapes.mitems:
|
||||||
|
for segment in shape.mitems:
|
||||||
|
segment += pos
|
||||||
|
mask.fillShapes(strokeShapes, windingRule)
|
||||||
|
|
||||||
|
proc strokePath*(
|
||||||
|
mask: Mask,
|
||||||
|
path: SomePath,
|
||||||
|
strokeWidth = 1.0,
|
||||||
|
mat: Mat3,
|
||||||
|
windingRule = wrNonZero
|
||||||
|
) =
|
||||||
|
var strokeShapes = strokeShapes(
|
||||||
|
parseSomePath(path),
|
||||||
|
strokeWidth,
|
||||||
|
windingRule
|
||||||
|
)
|
||||||
|
for shape in strokeShapes.mitems:
|
||||||
|
for segment in shape.mitems:
|
||||||
|
segment = mat * segment
|
||||||
|
mask.fillShapes(strokeShapes, windingRule)
|
||||||
|
|
||||||
when defined(release):
|
when defined(release):
|
||||||
{.pop.}
|
{.pop.}
|
||||||
|
|
BIN
tests/images/paths/pathRectangleMask.png
Normal file
BIN
tests/images/paths/pathRectangleMask.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 180 B |
BIN
tests/images/paths/pathRoundRectMask.png
Normal file
BIN
tests/images/paths/pathRoundRectMask.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 488 B |
|
@ -1,4 +1,4 @@
|
||||||
import pixie, chroma
|
import pixie, chroma, pixie/fileformats/png
|
||||||
|
|
||||||
block:
|
block:
|
||||||
let pathStr = """
|
let pathStr = """
|
||||||
|
@ -178,3 +178,27 @@ block:
|
||||||
image.fillPath(path, rgba(255, 0, 0, 255))
|
image.fillPath(path, rgba(255, 0, 0, 255))
|
||||||
image.toStraightAlpha()
|
image.toStraightAlpha()
|
||||||
image.writeFile("tests/images/paths/pathRoundRect.png")
|
image.writeFile("tests/images/paths/pathRoundRect.png")
|
||||||
|
|
||||||
|
block:
|
||||||
|
let
|
||||||
|
mask = newMask(100, 100)
|
||||||
|
pathStr = "M 10 10 H 90 V 90 H 10 L 10 10"
|
||||||
|
mask.fillPath(pathStr)
|
||||||
|
writeFile("tests/images/paths/pathRectangleMask.png", mask.encodePng())
|
||||||
|
|
||||||
|
block:
|
||||||
|
let
|
||||||
|
mask = newMask(100, 100)
|
||||||
|
r = 10.0
|
||||||
|
x = 10.0
|
||||||
|
y = 10.0
|
||||||
|
h = 80.0
|
||||||
|
w = 80.0
|
||||||
|
var path: Path
|
||||||
|
path.moveTo(x + r, y)
|
||||||
|
path.arcTo(x + w, y, x + w, y + h, r)
|
||||||
|
path.arcTo(x + w, y + h, x, y + h, r)
|
||||||
|
path.arcTo(x, y + h, x, y, r)
|
||||||
|
path.arcTo(x, y, x + w, y, r)
|
||||||
|
mask.fillPath(path)
|
||||||
|
writeFile("tests/images/paths/pathRoundRectMask.png", mask.encodePng())
|
||||||
|
|
Loading…
Reference in a new issue