2021-11-26 05:30:05 +00:00
|
|
|
import benchy, cairo, chroma, math, pixie, pixie/paths {.all.}, strformat
|
|
|
|
|
2021-12-13 00:19:07 +00:00
|
|
|
when defined(amd64) and not defined(pixieNoSimd):
|
|
|
|
import nimsimd/sse2, pixie/internal
|
|
|
|
|
2021-11-26 05:30:05 +00:00
|
|
|
proc doDiff(a, b: Image, name: string) =
|
|
|
|
let (diffScore, diffImage) = diff(a, b)
|
|
|
|
echo &"{name} score: {diffScore}"
|
|
|
|
diffImage.writeFile(&"{name}_diff.png")
|
2020-12-13 08:35:02 +00:00
|
|
|
|
2021-12-13 00:19:07 +00:00
|
|
|
when defined(release):
|
|
|
|
{.push checks: off.}
|
|
|
|
|
|
|
|
proc fillMask(
|
2022-02-12 18:29:28 +00:00
|
|
|
shapes: seq[seq[Vec2]], width, height: int, windingRule = NonZero
|
2021-12-13 00:19:07 +00:00
|
|
|
): Mask =
|
|
|
|
result = newMask(width, height)
|
|
|
|
|
|
|
|
let
|
|
|
|
segments = shapes.shapesToSegments()
|
|
|
|
bounds = computeBounds(segments).snapToPixels()
|
|
|
|
startY = max(0, bounds.y.int)
|
|
|
|
pathHeight = min(height, (bounds.y + bounds.h).int)
|
|
|
|
partitioning = partitionSegments(segments, startY, pathHeight)
|
|
|
|
width = width.float32
|
|
|
|
|
|
|
|
var
|
|
|
|
hits = newSeq[(float32, int16)](partitioning.maxEntryCount)
|
|
|
|
numHits: int
|
|
|
|
aa: bool
|
|
|
|
for y in startY ..< pathHeight:
|
|
|
|
computeCoverage(
|
|
|
|
cast[ptr UncheckedArray[uint8]](result.data[result.dataIndex(0, y)].addr),
|
|
|
|
hits,
|
|
|
|
numHits,
|
|
|
|
aa,
|
|
|
|
width,
|
|
|
|
y,
|
|
|
|
0,
|
|
|
|
partitioning,
|
|
|
|
windingRule
|
|
|
|
)
|
|
|
|
if not aa:
|
|
|
|
for (prevAt, at, count) in hits.walk(numHits, windingRule, y, width):
|
|
|
|
let
|
|
|
|
startIndex = result.dataIndex(prevAt.int, y)
|
|
|
|
len = at.int - prevAt.int
|
|
|
|
fillUnsafe(result.data, 255, startIndex, len)
|
|
|
|
|
|
|
|
proc fillMask*(
|
2022-02-12 18:29:28 +00:00
|
|
|
path: SomePath, width, height: int, windingRule = NonZero
|
2021-12-13 00:19:07 +00:00
|
|
|
): Mask =
|
|
|
|
## Returns a new mask with the path filled. This is a faster alternative
|
|
|
|
## to `newMask` + `fillPath`.
|
|
|
|
let shapes = parseSomePath(path, true, 1)
|
|
|
|
shapes.fillMask(width, height, windingRule)
|
|
|
|
|
|
|
|
proc fillImage(
|
|
|
|
shapes: seq[seq[Vec2]],
|
|
|
|
width, height: int,
|
|
|
|
color: SomeColor,
|
2022-02-12 18:29:28 +00:00
|
|
|
windingRule = NonZero
|
2021-12-13 00:19:07 +00:00
|
|
|
): Image =
|
|
|
|
result = newImage(width, height)
|
|
|
|
|
|
|
|
let
|
|
|
|
mask = shapes.fillMask(width, height, windingRule)
|
|
|
|
rgbx = color.rgbx()
|
|
|
|
|
|
|
|
var i: int
|
|
|
|
when defined(amd64) and not defined(pixieNoSimd):
|
|
|
|
let
|
|
|
|
colorVec = mm_set1_epi32(cast[int32](rgbx))
|
|
|
|
oddMask = mm_set1_epi16(cast[int16](0xff00))
|
|
|
|
div255 = mm_set1_epi16(cast[int16](0x8081))
|
|
|
|
vec255 = mm_set1_epi32(cast[int32](uint32.high))
|
|
|
|
vecZero = mm_setzero_si128()
|
|
|
|
colorVecEven = mm_slli_epi16(colorVec, 8)
|
|
|
|
colorVecOdd = mm_and_si128(colorVec, oddMask)
|
|
|
|
iterations = result.data.len div 16
|
|
|
|
for _ in 0 ..< iterations:
|
|
|
|
var coverageVec = mm_loadu_si128(mask.data[i].addr)
|
|
|
|
if mm_movemask_epi8(mm_cmpeq_epi16(coverageVec, vecZero)) != 0xffff:
|
|
|
|
if mm_movemask_epi8(mm_cmpeq_epi32(coverageVec, vec255)) == 0xffff:
|
|
|
|
for q in [0, 4, 8, 12]:
|
|
|
|
mm_storeu_si128(result.data[i + q].addr, colorVec)
|
|
|
|
else:
|
|
|
|
for q in [0, 4, 8, 12]:
|
|
|
|
var unpacked = unpackAlphaValues(coverageVec)
|
|
|
|
# Shift the coverages from `a` to `g` and `a` for multiplying
|
|
|
|
unpacked = mm_or_si128(unpacked, mm_srli_epi32(unpacked, 16))
|
|
|
|
|
|
|
|
var
|
|
|
|
sourceEven = mm_mulhi_epu16(colorVecEven, unpacked)
|
|
|
|
sourceOdd = mm_mulhi_epu16(colorVecOdd, unpacked)
|
|
|
|
sourceEven = mm_srli_epi16(mm_mulhi_epu16(sourceEven, div255), 7)
|
|
|
|
sourceOdd = mm_srli_epi16(mm_mulhi_epu16(sourceOdd, div255), 7)
|
|
|
|
|
|
|
|
mm_storeu_si128(
|
|
|
|
result.data[i + q].addr,
|
|
|
|
mm_or_si128(sourceEven, mm_slli_epi16(sourceOdd, 8))
|
|
|
|
)
|
|
|
|
|
|
|
|
coverageVec = mm_srli_si128(coverageVec, 4)
|
|
|
|
|
|
|
|
i += 16
|
|
|
|
|
|
|
|
let channels = [rgbx.r.uint32, rgbx.g.uint32, rgbx.b.uint32, rgbx.a.uint32]
|
|
|
|
for i in i ..< result.data.len:
|
|
|
|
let coverage = mask.data[i]
|
|
|
|
if coverage == 255:
|
|
|
|
result.data[i] = rgbx
|
|
|
|
elif coverage != 0:
|
|
|
|
result.data[i].r = ((channels[0] * coverage) div 255).uint8
|
|
|
|
result.data[i].g = ((channels[1] * coverage) div 255).uint8
|
|
|
|
result.data[i].b = ((channels[2] * coverage) div 255).uint8
|
|
|
|
result.data[i].a = ((channels[3] * coverage) div 255).uint8
|
|
|
|
|
|
|
|
proc fillImage*(
|
2022-02-12 18:29:28 +00:00
|
|
|
path: SomePath, width, height: int, color: SomeColor, windingRule = NonZero
|
2021-12-13 00:19:07 +00:00
|
|
|
): Image =
|
|
|
|
## Returns a new image with the path filled. This is a faster alternative
|
|
|
|
## to `newImage` + `fillPath`.
|
|
|
|
let shapes = parseSomePath(path, false, 1)
|
|
|
|
shapes.fillImage(width, height, color, windingRule)
|
|
|
|
|
|
|
|
proc strokeMask*(
|
|
|
|
path: SomePath,
|
|
|
|
width, height: int,
|
|
|
|
strokeWidth: float32 = 1.0,
|
2022-02-12 18:35:19 +00:00
|
|
|
lineCap = ButtCap,
|
|
|
|
lineJoin = MiterJoin,
|
2021-12-13 00:19:07 +00:00
|
|
|
miterLimit = defaultMiterLimit,
|
|
|
|
dashes: seq[float32] = @[]
|
|
|
|
): Mask =
|
|
|
|
## Returns a new mask with the path stroked. This is a faster alternative
|
|
|
|
## to `newImage` + `strokePath`.
|
|
|
|
let strokeShapes = strokeShapes(
|
|
|
|
parseSomePath(path, false, 1),
|
|
|
|
strokeWidth,
|
|
|
|
lineCap,
|
|
|
|
lineJoin,
|
|
|
|
miterLimit,
|
|
|
|
dashes,
|
|
|
|
1
|
|
|
|
)
|
2022-02-12 18:29:28 +00:00
|
|
|
result = strokeShapes.fillMask(width, height, NonZero)
|
2021-12-13 00:19:07 +00:00
|
|
|
|
|
|
|
proc strokeImage*(
|
|
|
|
path: SomePath,
|
|
|
|
width, height: int,
|
|
|
|
color: SomeColor,
|
|
|
|
strokeWidth: float32 = 1.0,
|
2022-02-12 18:35:19 +00:00
|
|
|
lineCap = ButtCap,
|
|
|
|
lineJoin = MiterJoin,
|
2021-12-13 00:19:07 +00:00
|
|
|
miterLimit = defaultMiterLimit,
|
|
|
|
dashes: seq[float32] = @[]
|
|
|
|
): Image =
|
|
|
|
## Returns a new image with the path stroked. This is a faster alternative
|
|
|
|
## to `newImage` + `strokePath`.
|
|
|
|
let strokeShapes = strokeShapes(
|
|
|
|
parseSomePath(path, false, 1),
|
|
|
|
strokeWidth,
|
|
|
|
lineCap,
|
|
|
|
lineJoin,
|
|
|
|
miterLimit,
|
|
|
|
dashes,
|
|
|
|
1
|
|
|
|
)
|
2022-02-12 18:29:28 +00:00
|
|
|
result = strokeShapes.fillImage(width, height, color, NonZero)
|
2021-12-13 00:19:07 +00:00
|
|
|
|
|
|
|
when defined(release):
|
|
|
|
{.pop.}
|
|
|
|
|
|
|
|
|
2021-06-03 08:48:11 +00:00
|
|
|
block:
|
2021-11-26 05:30:05 +00:00
|
|
|
let path = newPath()
|
|
|
|
path.moveTo(0, 0)
|
|
|
|
path.lineTo(1920, 0)
|
|
|
|
path.lineTo(1920, 1080)
|
|
|
|
path.lineTo(0, 1080)
|
|
|
|
path.closePath()
|
|
|
|
|
|
|
|
let shapes = path.commandsToShapes(true, 1)
|
|
|
|
|
2021-06-25 21:54:32 +00:00
|
|
|
let
|
2021-06-03 08:48:11 +00:00
|
|
|
surface = imageSurfaceCreate(FORMAT_ARGB32, 1920, 1080)
|
|
|
|
ctx = surface.create()
|
|
|
|
ctx.setSourceRgba(0, 0, 1, 1)
|
|
|
|
|
|
|
|
timeIt "cairo1":
|
|
|
|
ctx.newPath()
|
2021-11-26 05:30:05 +00:00
|
|
|
ctx.moveTo(shapes[0][0].x, shapes[0][0].y)
|
|
|
|
for shape in shapes:
|
|
|
|
for v in shape:
|
|
|
|
ctx.lineTo(v.x, v.y)
|
2021-06-03 08:48:11 +00:00
|
|
|
ctx.fill()
|
|
|
|
surface.flush()
|
|
|
|
|
|
|
|
# discard surface.writeToPng("cairo1.png")
|
|
|
|
|
2021-06-25 21:54:32 +00:00
|
|
|
let a = newImage(1920, 1080)
|
2021-06-03 08:48:11 +00:00
|
|
|
|
|
|
|
timeIt "pixie1":
|
2021-11-13 01:42:35 +00:00
|
|
|
let p = newPath()
|
2021-11-26 05:30:05 +00:00
|
|
|
p.moveTo(shapes[0][0])
|
|
|
|
for shape in shapes:
|
|
|
|
for v in shape:
|
|
|
|
p.lineTo(v)
|
2021-11-30 23:37:30 +00:00
|
|
|
a.fillPath(p, rgbx(0, 0, 255, 255))
|
2021-06-03 08:48:11 +00:00
|
|
|
|
|
|
|
# a.writeFile("pixie1.png")
|
|
|
|
|
|
|
|
block:
|
2021-11-26 05:30:05 +00:00
|
|
|
let path = newPath()
|
|
|
|
path.moveTo(500, 240)
|
|
|
|
path.lineTo(1500, 240)
|
|
|
|
path.lineTo(1920, 600)
|
|
|
|
path.lineTo(0, 600)
|
|
|
|
path.closePath()
|
|
|
|
|
|
|
|
let shapes = path.commandsToShapes(true, 1)
|
|
|
|
|
2021-06-25 21:54:32 +00:00
|
|
|
let
|
2021-06-03 08:48:11 +00:00
|
|
|
surface = imageSurfaceCreate(FORMAT_ARGB32, 1920, 1080)
|
|
|
|
ctx = surface.create()
|
|
|
|
|
|
|
|
timeIt "cairo2":
|
2021-11-26 05:30:05 +00:00
|
|
|
ctx.setSourceRgba(1, 1, 1, 1)
|
|
|
|
let operator = ctx.getOperator()
|
|
|
|
ctx.setOperator(OperatorSource)
|
|
|
|
ctx.paint()
|
|
|
|
ctx.setOperator(operator)
|
|
|
|
|
|
|
|
ctx.setSourceRgba(0, 0, 1, 1)
|
|
|
|
|
2021-06-03 08:48:11 +00:00
|
|
|
ctx.newPath()
|
2021-11-26 05:30:05 +00:00
|
|
|
ctx.moveTo(shapes[0][0].x, shapes[0][0].y)
|
|
|
|
for shape in shapes:
|
|
|
|
for v in shape:
|
|
|
|
ctx.lineTo(v.x, v.y)
|
2021-06-03 08:48:11 +00:00
|
|
|
ctx.fill()
|
2021-11-26 05:30:05 +00:00
|
|
|
surface.flush()
|
2020-12-13 08:35:02 +00:00
|
|
|
|
2021-06-03 08:48:11 +00:00
|
|
|
# discard surface.writeToPng("cairo2.png")
|
2020-12-13 08:35:02 +00:00
|
|
|
|
2021-06-25 21:54:32 +00:00
|
|
|
let a = newImage(1920, 1080)
|
2020-12-13 08:35:02 +00:00
|
|
|
|
2021-06-03 08:48:11 +00:00
|
|
|
timeIt "pixie2":
|
2021-11-30 23:37:30 +00:00
|
|
|
a.fill(rgbx(255, 255, 255, 255))
|
2021-11-26 05:30:05 +00:00
|
|
|
|
2021-11-13 01:42:35 +00:00
|
|
|
let p = newPath()
|
2021-11-26 05:30:05 +00:00
|
|
|
p.moveTo(shapes[0][0])
|
|
|
|
for shape in shapes:
|
|
|
|
for v in shape:
|
|
|
|
p.lineTo(v)
|
2021-11-30 23:37:30 +00:00
|
|
|
a.fillPath(p, rgbx(0, 0, 255, 255))
|
2020-12-13 08:35:02 +00:00
|
|
|
|
2021-06-03 08:48:11 +00:00
|
|
|
# a.writeFile("pixie2.png")
|
2021-06-26 01:49:05 +00:00
|
|
|
|
2021-11-26 05:30:05 +00:00
|
|
|
block:
|
|
|
|
let path = parsePath("""
|
|
|
|
M 100,300
|
|
|
|
A 200,200 0,0,1 500,300
|
|
|
|
A 200,200 0,0,1 900,300
|
|
|
|
Q 900,600 500,900
|
|
|
|
Q 100,600 100,300 z
|
|
|
|
""")
|
|
|
|
|
|
|
|
let shapes = path.commandsToShapes(true, 1)
|
|
|
|
|
|
|
|
let
|
|
|
|
surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
|
|
|
|
ctx = surface.create()
|
|
|
|
|
|
|
|
timeIt "cairo3":
|
|
|
|
ctx.setSourceRgba(1, 1, 1, 1)
|
|
|
|
let operator = ctx.getOperator()
|
|
|
|
ctx.setOperator(OperatorSource)
|
|
|
|
ctx.paint()
|
|
|
|
ctx.setOperator(operator)
|
|
|
|
|
|
|
|
ctx.setSourceRgba(1, 0, 0, 1)
|
|
|
|
|
|
|
|
ctx.newPath()
|
|
|
|
ctx.moveTo(shapes[0][0].x, shapes[0][0].y)
|
|
|
|
for shape in shapes:
|
|
|
|
for v in shape:
|
|
|
|
ctx.lineTo(v.x, v.y)
|
|
|
|
ctx.fill()
|
|
|
|
surface.flush()
|
|
|
|
|
|
|
|
# discard surface.writeToPng("cairo3.png")
|
|
|
|
|
|
|
|
let a = newImage(1000, 1000)
|
|
|
|
|
|
|
|
timeIt "pixie3":
|
2021-11-30 23:37:30 +00:00
|
|
|
a.fill(rgbx(255, 255, 255, 255))
|
2021-11-26 05:30:05 +00:00
|
|
|
|
|
|
|
let p = newPath()
|
|
|
|
p.moveTo(shapes[0][0])
|
|
|
|
for shape in shapes:
|
|
|
|
for v in shape:
|
|
|
|
p.lineTo(v)
|
2021-11-30 23:37:30 +00:00
|
|
|
a.fillPath(p, rgbx(255, 0, 0, 255))
|
2021-11-26 05:30:05 +00:00
|
|
|
|
|
|
|
# a.writeFile("pixie3.png")
|
|
|
|
|
|
|
|
# doDiff(readImage("cairo3.png"), a, "cairo3")
|
2021-11-29 05:58:54 +00:00
|
|
|
|
|
|
|
block:
|
|
|
|
let path = newPath()
|
|
|
|
path.roundedRect(200, 200, 600, 600, 10, 10, 10, 10)
|
|
|
|
|
|
|
|
let shapes = path.commandsToShapes(true, 1)
|
|
|
|
|
2021-12-01 06:18:25 +00:00
|
|
|
# let
|
|
|
|
# surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
|
|
|
|
# ctx = surface.create()
|
2021-11-29 05:58:54 +00:00
|
|
|
|
2021-11-30 22:00:30 +00:00
|
|
|
# timeIt "cairo4":
|
2021-12-01 06:18:25 +00:00
|
|
|
# ctx.setSourceRgba(0, 0, 0, 0)
|
|
|
|
# let operator = ctx.getOperator()
|
|
|
|
# ctx.setOperator(OperatorSource)
|
|
|
|
# ctx.paint()
|
|
|
|
# ctx.setOperator(operator)
|
|
|
|
|
|
|
|
timeIt "cairo4":
|
|
|
|
let
|
|
|
|
surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
|
|
|
|
ctx = surface.create()
|
2021-11-30 22:00:30 +00:00
|
|
|
|
2021-11-29 08:13:31 +00:00
|
|
|
ctx.setSourceRgba(1, 0, 0, 0.5)
|
2021-11-29 05:58:54 +00:00
|
|
|
|
|
|
|
ctx.newPath()
|
|
|
|
ctx.moveTo(shapes[0][0].x, shapes[0][0].y)
|
|
|
|
for shape in shapes:
|
|
|
|
for v in shape:
|
|
|
|
ctx.lineTo(v.x, v.y)
|
|
|
|
ctx.fill()
|
|
|
|
surface.flush()
|
|
|
|
|
2021-11-29 08:13:31 +00:00
|
|
|
# discard surface.writeToPng("cairo4.png")
|
2021-11-29 05:58:54 +00:00
|
|
|
|
2021-11-30 22:00:30 +00:00
|
|
|
var a: Image
|
2021-11-29 05:58:54 +00:00
|
|
|
timeIt "pixie4":
|
2021-11-30 22:26:28 +00:00
|
|
|
a = newImage(1000, 1000)
|
2021-11-29 05:58:54 +00:00
|
|
|
|
|
|
|
let p = newPath()
|
|
|
|
p.moveTo(shapes[0][0])
|
|
|
|
for shape in shapes:
|
|
|
|
for v in shape:
|
|
|
|
p.lineTo(v)
|
2021-11-30 23:37:30 +00:00
|
|
|
a.fillPath(p, rgbx(127, 0, 0, 127))
|
2021-11-29 05:58:54 +00:00
|
|
|
|
2021-11-29 08:13:31 +00:00
|
|
|
# a.writeFile("pixie4.png")
|
2021-11-29 05:58:54 +00:00
|
|
|
|
2021-11-29 08:13:31 +00:00
|
|
|
# doDiff(readImage("cairo4.png"), a, "4")
|
2021-11-29 09:38:49 +00:00
|
|
|
|
2021-12-13 00:23:31 +00:00
|
|
|
var b: Image
|
2022-02-14 04:02:37 +00:00
|
|
|
let paint = newPaint(SolidPaint)
|
2021-12-13 00:23:31 +00:00
|
|
|
paint.color = color(1, 0, 0, 0.5)
|
2022-02-14 04:02:37 +00:00
|
|
|
paint.blendMode = OverwriteBlend
|
2021-12-13 00:23:31 +00:00
|
|
|
|
|
|
|
timeIt "pixie4 overwrite":
|
|
|
|
b = newImage(1000, 1000)
|
|
|
|
|
|
|
|
let p = newPath()
|
|
|
|
p.moveTo(shapes[0][0])
|
|
|
|
for shape in shapes:
|
|
|
|
for v in shape:
|
|
|
|
p.lineTo(v)
|
|
|
|
b.fillPath(p, paint)
|
|
|
|
|
|
|
|
# b.writeFile("b.png")
|
|
|
|
|
2021-11-29 09:38:49 +00:00
|
|
|
timeIt "pixie4 mask":
|
2021-11-30 22:00:30 +00:00
|
|
|
let mask = newMask(1000, 1000)
|
2021-11-29 09:38:49 +00:00
|
|
|
|
|
|
|
let p = newPath()
|
|
|
|
p.moveTo(shapes[0][0])
|
|
|
|
for shape in shapes:
|
|
|
|
for v in shape:
|
|
|
|
p.lineTo(v)
|
|
|
|
mask.fillPath(p)
|
2021-11-30 22:00:30 +00:00
|
|
|
|
|
|
|
var tmp: Image
|
|
|
|
timeIt "pixie fillImage":
|
2021-11-30 23:46:22 +00:00
|
|
|
let p = newPath()
|
|
|
|
p.moveTo(shapes[0][0])
|
|
|
|
for shape in shapes:
|
|
|
|
for v in shape:
|
|
|
|
p.lineTo(v)
|
|
|
|
|
|
|
|
tmp = p.fillImage(1000, 1000, rgbx(127, 0, 0, 127))
|
2021-11-30 22:00:30 +00:00
|
|
|
|
|
|
|
# tmp.writeFile("tmp.png")
|