context isPointInPath isPointInStroke

This commit is contained in:
Ryan Oldenburg 2021-07-18 13:20:22 -05:00
parent 5ccfdb574e
commit d097fda55b
5 changed files with 191 additions and 6 deletions

View file

@ -1,4 +1,4 @@
version = "2.1.0"
version = "2.1.1"
author = "Andre von Houck and Ryan Oldenburg"
description = "Full-featured 2d graphics library for Nim."
license = "MIT"

View file

@ -647,3 +647,55 @@ proc arcTo*(ctx: Context, x1, y1, x2, y2, radius: float32) =
proc arcTo*(ctx: Context, a, b: Vec2, r: float32) =
## Adds a circular arc using the given control points and radius.
ctx.path.arcTo(a, b, r)
proc isPointInPath*(
ctx: Context, path: Path, pos: Vec2, windingRule = wrNonZero
): bool =
## Returns whether or not the specified point is contained in the current path.
path.fillOverlaps(pos, ctx.mat, windingRule)
proc isPointInPath*(
ctx: Context, path: Path, x, y: float32, windingRule = wrNonZero
): bool {.inline.} =
## Returns whether or not the specified point is contained in the current path.
ctx.isPointInPath(path, vec2(x, y), windingRule)
proc isPointInPath*(
ctx: Context, pos: Vec2, windingRule = wrNonZero
): bool {.inline.} =
## Returns whether or not the specified point is contained in the current path.
ctx.isPointInPath(ctx.path, pos, windingRule)
proc isPointInPath*(
ctx: Context, x, y: float32, windingRule = wrNonZero
): bool {.inline.} =
## Returns whether or not the specified point is contained in the current path.
ctx.isPointInPath(ctx.path, vec2(x, y), windingRule)
proc isPointInStroke*(ctx: Context, path: Path, pos: Vec2): bool =
## Returns whether or not the specified point is inside the area contained
## by the stroking of a path.
path.strokeOverlaps(
pos,
ctx.mat,
ctx.lineWidth,
ctx.lineCap,
ctx.lineJoin,
ctx.miterLimit,
ctx.lineDash
)
proc isPointInStroke*(ctx: Context, path: Path, x, y: float32): bool {.inline.} =
## Returns whether or not the specified point is inside the area contained
## by the stroking of a path.
ctx.isPointInStroke(path, vec2(x, y))
proc isPointInStroke*(ctx: Context, pos: Vec2): bool {.inline.} =
## Returns whether or not the specified point is inside the area contained
## by the stroking of a path.
ctx.isPointInStroke(ctx.path, pos)
proc isPointInStroke*(ctx: Context, x, y: float32): bool {.inline.} =
## Returns whether or not the specified point is inside the area contained
## by the stroking of a path.
ctx.isPointInStroke(ctx.path, vec2(x, y))

View file

@ -1029,7 +1029,7 @@ proc requiresAntiAliasing(segments: seq[(Segment, int16)]): bool =
template hasFractional(v: float32): bool =
v - trunc(v) != 0
for i in 0 ..< segments.len: # For arc
for i in 0 ..< segments.len: # For gc:arc
let segment = segments[i][0]
if segment.at.x != segment.to.x or
segment.at.x.hasFractional() or # at.x and to.x are the same
@ -1045,7 +1045,7 @@ proc computePixelBounds(segments: seq[(Segment, int16)]): Rect =
xMax = float32.low
yMin = float32.high
yMax = float32.low
for i in 0 ..< segments.len: # For arc
for i in 0 ..< segments.len: # For gc:arc
let segment = segments[i][0]
xMin = min(xMin, min(segment.at.x, segment.to.x))
xMax = max(xMax, max(segment.at.x, segment.to.x))
@ -1153,7 +1153,7 @@ iterator walk(
var
prevAt: float32
count: int32
for i in 0 ..< numHits:
for i in 0 ..< numHits: # For gc:arc
let (at, winding) = hits[i]
if windingRule == wrNonZero and
(count != 0) == (count + winding != 0) and
@ -1201,13 +1201,13 @@ proc computeCoverages(
scanline.a.y = yLine
scanline.b.y = yLine
numHits = 0
for i in 0 ..< partitioning.partitions[partitionIndex].len: # For arc
for i in 0 ..< partitioning.partitions[partitionIndex].len: # For gc:arc
let
segment = partitioning.partitions[partitionIndex][i][0]
winding = partitioning.partitions[partitionIndex][i][1]
if segment.at.y <= scanline.a.y and segment.to.y >= scanline.a.y:
var at: Vec2
if segment.to != at and scanline.intersects(segment, at):
if scanline.intersects(segment, at) and segment.to != at:
if numHits == hits.len:
hits.setLen(hits.len * 2)
hits[numHits] = (min(at.x, size.x), winding)
@ -1889,5 +1889,71 @@ proc strokePath*(
fill.draw(mask)
image.draw(fill, blendMode = paint.blendMode)
proc overlaps(
shapes: seq[seq[Vec2]],
test: Vec2,
windingRule: WindingRule
): bool =
var hits: seq[(float32, int16)]
let
scanline = line(vec2(0, test.y), vec2(1000, test.y))
segments = shapes.shapesToSegments()
for i in 0 ..< segments.len: # For gc:arc
let
segment = segments[i][0]
winding = segments[i][1]
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:
hits.add((at.x, winding))
if hits.len > 32:
quickSort(hits, 0, hits.high)
else:
insertionSort(hits, hits.high)
var count: int
for i in 0 ..< hits.len: # For gc:arc
let (at, winding) = hits[i]
if at > test.x:
result = shouldFill(windingRule, count)
break
count += winding
proc fillOverlaps*(
path: Path,
test: Vec2,
transform: Vec2 | Mat3 = vec2(), ## Applied to the path, not the test point.
windingRule = wrNonZero
): bool =
## Returns whether or not the specified point is contained in the current path.
var shapes = parseSomePath(path, true, transform.pixelScale())
shapes.transform(transform)
shapes.overlaps(test, windingRule)
proc strokeOverlaps*(
path: Path,
test: Vec2,
transform: Vec2 | Mat3 = vec2(), ## Applied to the path, not the test point.
strokeWidth = 1.0,
lineCap = lcButt,
lineJoin = ljMiter,
miterLimit = defaultMiterLimit,
dashes: seq[float32] = @[],
): bool =
## Returns whether or not the specified point is inside the area contained
## by the stroking of a path.
var strokeShapes = strokeShapes(
parseSomePath(path, false, transform.pixelScale()),
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes
)
strokeShapes.transform(transform)
strokeShapes.overlaps(test, wrNonZero)
when defined(release):
{.pop.}

View file

@ -602,3 +602,32 @@ block:
rhino = readImage("tests/images/rhino.png")
ctx.drawImage(rhino, rect(33, 71, 104, 124), rect(21, 20, 87, 104));
image.writeFile("tests/images/context/draw_image_rhino2.png")
block:
let
image = newImage(100, 100)
ctx = newContext(image)
ctx.rect(10, 10, 100, 100)
doAssert ctx.isPointInPath(30, 70)
block:
let
image = newImage(300, 150)
ctx = newContext(image)
ctx.arc(150, 75, 50, 0, 2 * PI)
doAssert ctx.isPointInPath(150, 50)
block:
let
image = newImage(100, 100)
ctx = newContext(image)
ctx.rect(10, 10, 100, 100)
doAssert ctx.isPointInStroke(50, 10)
block:
let
image = newImage(300, 150)
ctx = newContext(image)
ctx.ellipse(150, 75, 40, 60)
ctx.lineWidth = 25
doAssert ctx.isPointInStroke(110, 75)

View file

@ -530,3 +530,41 @@ block:
ctx.stroke();
surface.writeFile("tests/images/paths/arcTo3.png")
block:
var path: Path
path.rect(0, 0, 10, 10)
doAssert path.fillOverlaps(vec2(5, 5))
doAssert path.fillOverlaps(vec2(0, 0))
doAssert path.fillOverlaps(vec2(9, 0))
doAssert path.fillOverlaps(vec2(0, 9))
doAssert not path.fillOverlaps(vec2(10, 10))
block:
var path: Path
path.ellipse(20, 20, 20, 10)
doAssert not path.fillOverlaps(vec2(0, 0))
doAssert path.fillOverlaps(vec2(20, 20))
doAssert path.fillOverlaps(vec2(10, 20))
doAssert path.fillOverlaps(vec2(30, 20))
block:
var path: Path
path.rect(10, 10, 10, 10)
doAssert path.strokeOverlaps(vec2(10, 10))
doAssert path.strokeOverlaps(vec2(20.1, 20.1))
doAssert not path.strokeOverlaps(vec2(5, 5))
block:
var path: Path
path.ellipse(20, 20, 20, 10)
doAssert not path.strokeOverlaps(vec2(0, 0))
doAssert not path.strokeOverlaps(vec2(20, 20))
doAssert path.strokeOverlaps(vec2(0, 20))
doAssert path.strokeOverlaps(vec2(40, 20))
doAssert path.strokeOverlaps(vec2(19.8, 30.2))
doAssert not path.strokeOverlaps(vec2(19.4, 30.6))