Add dashes. ()

* Add dashes.
* Miter limits as ratio everywhere.
* Remove some fill and stroke overloads.
This commit is contained in:
treeform 2021-05-23 18:46:19 -07:00 committed by GitHub
parent 08dac8e241
commit 6fb1323101
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 153 additions and 117 deletions

View file

@ -193,7 +193,11 @@ proc clearRect*(ctx: Context, rect: Rect) =
## Erases the pixels in a rectangular area.
var path: Path
path.rect(rect)
ctx.image.fillPath(path, rgbx(0, 0, 0, 0), ctx.mat, blendMode = bmOverwrite)
ctx.image.fillPath(
path,
Paint(kind: pkSolid, color:rgbx(0, 0, 0, 0), blendMode: bmOverwrite),
ctx.mat
)
proc clearRect*(ctx: Context, x, y, width, height: float32) {.inline.} =
## Erases the pixels in a rectangular area.

View file

@ -1,6 +1,6 @@
## Load SVG files.
import chroma, pixie/common, pixie/images, pixie/paths, strutils, vmath,
import chroma, pixie/common, pixie/images, pixie/paths, pixie/paints, strutils, vmath,
xmlparser, xmltree
const

View file

@ -34,7 +34,10 @@ converter parseSomePaint*(paint: SomePaint): Paint {.inline.} =
when type(paint) is string:
Paint(kind: pkSolid, color: parseHtmlColor(paint).rgbx())
elif type(paint) is SomeColor:
Paint(kind: pkSolid, color: paint.rgbx())
when type(paint) is ColorRGBX:
Paint(kind: pkSolid, color: paint)
else:
Paint(kind: pkSolid, color: paint.rgbx())
elif type(paint) is Paint:
paint

View file

@ -41,12 +41,15 @@ const epsilon = 0.0001 * PI ## Tiny value used for some computations.
when defined(release):
{.push checks: off.}
proc maxScale(m: Mat3): float32 =
## What is the largest scale factor of this matrix?
max(
vec2(m[0, 0], m[0, 1]).length,
vec2(m[1, 0], m[1, 1]).length
)
proc pixelScale(transform: Vec2 | Mat3): float32 =
## What is the largest scale factor of this transform?
when type(transform) is Vec2:
return 1.0
else:
max(
vec2(transform[0, 0], transform[0, 1]).length,
vec2(transform[1, 0], transform[1, 1]).length
)
proc isRelative(kind: PathCommandKind): bool =
kind in {
@ -1317,16 +1320,27 @@ proc fillShapes(
mask.setValueUnsafe(x, y, blended)
inc x
proc miterLimitToAngle*(limit: float32): float32 =
## Converts milter-limit-ratio to miter-limit-angle.
arcsin(1 / limit) * 2
proc angleToMiterLimit*(angle: float32): float32 =
## Converts miter-limit-angle to milter-limit-ratio.
1 / sin(angle / 2)
proc strokeShapes(
shapes: seq[seq[Vec2]],
strokeWidth: float32,
lineCap: LineCap,
lineJoin: LineJoin,
miterAngleLimit = degToRad(28.96)
miterLimit: float32,
dashes: seq[float32]
): seq[seq[Vec2]] =
if strokeWidth == 0:
return
let miterAngleLimit = miterLimitToAngle(miterLimit)
let halfStroke = strokeWidth / 2
proc makeCircle(at: Vec2): seq[Vec2] =
@ -1413,7 +1427,25 @@ proc strokeShapes(
pos = shape[i]
prevPos = shape[i - 1]
shapeStroke.add(makeRect(prevPos, pos))
if dashes.len > 0:
var dashes = dashes
if dashes.len mod 2 != 0:
dashes.add(dashes[^1])
var distance = dist(prevPos, pos)
let dir = dir(pos, prevPos)
var currPos = prevPos
block dashLoop:
while true:
for i, d in dashes:
if i mod 2 == 0:
let d = min(distance, d)
shapeStroke.add(makeRect(currPos, currPos + dir * d))
currPos += dir * d
distance -= d
if distance <= 0:
break dashLoop
else:
shapeStroke.add(makeRect(prevPos, pos))
# If we need a line join
if i < shape.len - 1:
@ -1459,53 +1491,14 @@ proc transform(shapes: var seq[seq[Vec2]], transform: Vec2 | Mat3) =
for segment in shape.mitems:
segment = transform * segment
proc fillPath*(
image: Image,
path: SomePath,
color: SomeColor,
windingRule = wrNonZero,
blendMode = bmNormal
) {.inline.} =
## Fills a path.
image.fillShapes(parseSomePath(path), color, windingRule, blendMode)
proc fillPath*(
image: Image,
path: SomePath,
color: SomeColor,
transform: Vec2 | Mat3,
windingRule = wrNonZero,
blendMode = bmNormal
) =
## Fills a path.
when type(transform) is Mat3:
let pixelScale = transform.maxScale()
else:
let pixelScale = 1.0
var shapes = parseSomePath(path, pixelScale)
shapes.transform(transform)
image.fillShapes(shapes, color, windingRule, blendMode)
proc fillPath*(
mask: Mask,
path: SomePath,
windingRule = wrNonZero
) {.inline.} =
## Fills a path.
mask.fillShapes(parseSomePath(path), windingRule)
proc fillPath*(
mask: Mask,
path: SomePath,
transform: Vec2 | Mat3,
transform: Vec2 | Mat3 = vec2(),
windingRule = wrNonZero
) =
## Fills a path.
when type(transform) is Mat3:
let pixelScale = transform.maxScale()
else:
let pixelScale = 1.0
var shapes = parseSomePath(path, pixelScale)
var shapes = parseSomePath(path, transform.pixelScale())
shapes.transform(transform)
mask.fillShapes(shapes, windingRule)
@ -1518,7 +1511,9 @@ proc fillPath*(
) =
## Fills a path.
if paint.kind == pkSolid:
image.fillPath(path, paint.color, transform, windingRule)
var shapes = parseSomePath(path, transform.pixelScale())
shapes.transform(transform)
image.fillShapes(shapes, paint.color, windingRule, paint.blendMode)
return
let
@ -1545,69 +1540,23 @@ proc fillPath*(
image.draw(fill, blendMode = paint.blendMode)
proc strokePath*(
image: Image,
mask: Mask,
path: SomePath,
color: SomeColor,
transform: Vec2 | Mat3 = vec2(),
strokeWidth = 1.0,
lineCap = lcButt,
lineJoin = ljMiter,
blendMode = bmNormal
miterLimit: float32 = 4,
dashes: seq[float32] = @[],
) =
## Strokes a path.
let strokeShapes = strokeShapes(
parseSomePath(path), strokeWidth, lineCap, lineJoin
)
image.fillShapes(strokeShapes, color, wrNonZero, blendMode)
proc strokePath*(
image: Image,
path: SomePath,
color: SomeColor,
transform: Vec2 | Mat3,
strokeWidth = 1.0,
lineCap = lcButt,
lineJoin = ljMiter,
blendMode = bmNormal
) =
## Strokes a path.
when type(transform) is Mat3:
let pixelScale = transform.maxScale()
else:
let pixelScale = 1.0
var strokeShapes = strokeShapes(
parseSomePath(path, pixelScale), strokeWidth, lineCap, lineJoin
)
strokeShapes.transform(transform)
image.fillShapes(strokeShapes, color, wrNonZero, blendMode)
proc strokePath*(
mask: Mask,
path: SomePath,
strokeWidth = 1.0,
lineCap = lcButt,
lineJoin = ljMiter
) =
## Strokes a path.
let strokeShapes = strokeShapes(
parseSomePath(path), strokeWidth, lineCap, lineJoin
)
mask.fillShapes(strokeShapes, wrNonZero)
proc strokePath*(
mask: Mask,
path: SomePath,
transform: Vec2 | Mat3,
strokeWidth = 1.0,
lineCap = lcButt,
lineJoin = ljMiter
) =
## Strokes a path.
when type(transform) is Mat3:
let pixelScale = transform.maxScale()
else:
let pixelScale = 1.0
var strokeShapes = strokeShapes(
parseSomePath(path, pixelScale), strokeWidth, lineCap, lineJoin
parseSomePath(path, transform.pixelScale()),
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes
)
strokeShapes.transform(transform)
mask.fillShapes(strokeShapes, wrNonZero)
@ -1619,20 +1568,38 @@ proc strokePath*(
transform: Vec2 | Mat3 = vec2(),
strokeWidth = 1.0,
lineCap = lcButt,
lineJoin = ljMiter
lineJoin = ljMiter,
miterLimit: float32 = 4,
dashes: seq[float32] = @[]
) =
## Fills a path.
## Strokes a path.
if paint.kind == pkSolid:
image.strokePath(
path, paint.color, transform, strokeWidth, lineCap, lineJoin
var strokeShapes = strokeShapes(
parseSomePath(path, transform.pixelScale()),
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes
)
strokeShapes.transform(transform)
image.fillShapes(
strokeShapes, paint.color, wrNonZero, blendMode = paint.blendMode)
return
let
mask = newMask(image.width, image.height)
fill = newImage(image.width, image.height)
mask.strokePath(parseSomePath(path), transform)
mask.strokePath(
parseSomePath(path),
transform,
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes
)
case paint.kind:
of pkSolid:

Binary file not shown.

After

(image error) Size: 824 B

Binary file not shown.

After

(image error) Size: 597 B

Binary file not shown.

After

(image error) Size: 625 B

Binary file not shown.

After

(image error) Size: 625 B

Binary file not shown.

After

(image error) Size: 642 B

Binary file not shown.

After

(image error) Size: 557 B

Binary file not shown.

After

(image error) Size: 700 B

Binary file not shown.

After

(image error) Size: 531 B

View file

@ -1,4 +1,4 @@
import chroma, pixie, pixie/fileformats/png
import chroma, pixie, pixie/fileformats/png, strformat
block:
let pathStr = """
@ -47,7 +47,7 @@ block:
image = newImage(100, 100)
pathStr = "M 10 10 L 90 90"
color = rgba(255, 0, 0, 255)
image.strokePath(pathStr, color, 10)
image.strokePath(pathStr, color, strokeWidth=10)
image.writeFile("tests/images/paths/pathStroke1.png")
block:
@ -55,7 +55,7 @@ block:
image = newImage(100, 100)
pathStr = "M 10 10 L 50 60 90 90"
color = rgba(255, 0, 0, 255)
image.strokePath(pathStr, color, 10)
image.strokePath(pathStr, color, strokeWidth=10)
image.writeFile("tests/images/paths/pathStroke2.png")
block:
@ -256,6 +256,68 @@ block:
image.writeFile("tests/images/paths/lcSquare.png")
block:
let
image = newImage(60, 120)
path = parsePath("M 0 0 L 50 0")
image.fill(rgba(255, 255, 255, 255))
image.strokePath(
path, rgba(0, 0, 0, 255), vec2(5, 5), 10, lcButt, ljBevel,
)
image.strokePath(
path, rgba(0, 0, 0, 255), vec2(5, 25), 10, lcButt, ljBevel,
dashes = @[2.float32,2]
)
image.strokePath(
path, rgba(0, 0, 0, 255), vec2(5, 45), 10, lcButt, ljBevel,
dashes = @[4.float32,4]
)
image.strokePath(
path, rgba(0, 0, 0, 255), vec2(5, 65), 10, lcButt, ljBevel,
dashes = @[2.float32, 4, 6, 2]
)
image.strokePath(
path, rgba(0, 0, 0, 255), vec2(5, 85), 10, lcButt, ljBevel,
dashes = @[1.float32]
)
image.strokePath(
path, rgba(0, 0, 0, 255), vec2(5, 105), 10, lcButt, ljBevel,
dashes = @[1.float32, 2, 3, 4, 5, 6, 7, 8, 9]
)
image.writeFile("tests/images/paths/dashes.png")
block:
proc miterTest(angle, limit: float32) =
let
image = newImage(60, 60)
image.fill(rgba(255, 255, 255, 255))
var path: Path
path.moveTo(-20, 0)
path.lineTo(0, 0)
let th = angle.float32.degToRad() + PI/2
path.lineTo(sin(th)*20, cos(th)*20)
image.strokePath(
path, rgba(0, 0, 0, 255), vec2(30, 30), 8, lcButt, ljMiter,
miterLimit = limit
)
image.writeFile(&"tests/images/paths/miterLimit_{angle.int}deg_{limit:0.2f}num.png")
miterTest(10, 2)
miterTest(145, 2)
miterTest(155, 2)
miterTest(165, 2)
miterTest(165, 10)
miterTest(145, 3.32)
miterTest(145, 3.33)
# Potential error cases, ensure they do not crash
block: