Merge pull request #245 from guzba/master
context isPointInPath isPointInStroke
This commit is contained in:
commit
61d93113ab
5 changed files with 196 additions and 10 deletions
|
@ -1,4 +1,4 @@
|
||||||
version = "2.1.0"
|
version = "2.1.1"
|
||||||
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"
|
||||||
|
|
|
@ -647,3 +647,55 @@ proc arcTo*(ctx: Context, x1, y1, x2, y2, radius: float32) =
|
||||||
proc arcTo*(ctx: Context, a, b: Vec2, r: float32) =
|
proc arcTo*(ctx: Context, a, b: Vec2, r: float32) =
|
||||||
## Adds a circular arc using the given control points and radius.
|
## Adds a circular arc using the given control points and radius.
|
||||||
ctx.path.arcTo(a, b, r)
|
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))
|
||||||
|
|
|
@ -1029,7 +1029,7 @@ proc requiresAntiAliasing(segments: seq[(Segment, int16)]): bool =
|
||||||
template hasFractional(v: float32): bool =
|
template hasFractional(v: float32): bool =
|
||||||
v - trunc(v) != 0
|
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]
|
let segment = segments[i][0]
|
||||||
if segment.at.x != segment.to.x or
|
if segment.at.x != segment.to.x or
|
||||||
segment.at.x.hasFractional() or # at.x and to.x are the same
|
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
|
xMax = float32.low
|
||||||
yMin = float32.high
|
yMin = float32.high
|
||||||
yMax = float32.low
|
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]
|
let segment = segments[i][0]
|
||||||
xMin = min(xMin, min(segment.at.x, segment.to.x))
|
xMin = min(xMin, min(segment.at.x, segment.to.x))
|
||||||
xMax = max(xMax, max(segment.at.x, segment.to.x))
|
xMax = max(xMax, max(segment.at.x, segment.to.x))
|
||||||
|
@ -1153,7 +1153,7 @@ iterator walk(
|
||||||
var
|
var
|
||||||
prevAt: float32
|
prevAt: float32
|
||||||
count: int32
|
count: int32
|
||||||
for i in 0 ..< numHits:
|
for i in 0 ..< numHits: # For gc:arc
|
||||||
let (at, winding) = hits[i]
|
let (at, winding) = hits[i]
|
||||||
if windingRule == wrNonZero and
|
if windingRule == wrNonZero and
|
||||||
(count != 0) == (count + winding != 0) and
|
(count != 0) == (count + winding != 0) and
|
||||||
|
@ -1201,13 +1201,14 @@ proc computeCoverages(
|
||||||
scanline.a.y = yLine
|
scanline.a.y = yLine
|
||||||
scanline.b.y = yLine
|
scanline.b.y = yLine
|
||||||
numHits = 0
|
numHits = 0
|
||||||
for i in 0 ..< partitioning.partitions[partitionIndex].len: # For arc
|
for i in 0 ..< partitioning.partitions[partitionIndex].len: # For gc:arc
|
||||||
let
|
let
|
||||||
segment = partitioning.partitions[partitionIndex][i][0]
|
segment = partitioning.partitions[partitionIndex][i][0]
|
||||||
winding = partitioning.partitions[partitionIndex][i][1]
|
winding = partitioning.partitions[partitionIndex][i][1]
|
||||||
if segment.at.y <= scanline.a.y and segment.to.y >= scanline.a.y:
|
if segment.at.y <= scanline.a.y and segment.to.y >= scanline.a.y:
|
||||||
var at: Vec2
|
var at: Vec2
|
||||||
if segment.to != at and scanline.intersects(segment, at):
|
if scanline.intersects(segment, at):
|
||||||
|
if segment.to != at:
|
||||||
if numHits == hits.len:
|
if numHits == hits.len:
|
||||||
hits.setLen(hits.len * 2)
|
hits.setLen(hits.len * 2)
|
||||||
hits[numHits] = (min(at.x, size.x), winding)
|
hits[numHits] = (min(at.x, size.x), winding)
|
||||||
|
@ -1889,5 +1890,71 @@ proc strokePath*(
|
||||||
fill.draw(mask)
|
fill.draw(mask)
|
||||||
image.draw(fill, blendMode = paint.blendMode)
|
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):
|
||||||
|
if 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:
|
||||||
|
return shouldFill(windingRule, count)
|
||||||
|
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):
|
when defined(release):
|
||||||
{.pop.}
|
{.pop.}
|
||||||
|
|
|
@ -602,3 +602,32 @@ block:
|
||||||
rhino = readImage("tests/images/rhino.png")
|
rhino = readImage("tests/images/rhino.png")
|
||||||
ctx.drawImage(rhino, rect(33, 71, 104, 124), rect(21, 20, 87, 104));
|
ctx.drawImage(rhino, rect(33, 71, 104, 124), rect(21, 20, 87, 104));
|
||||||
image.writeFile("tests/images/context/draw_image_rhino2.png")
|
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)
|
||||||
|
|
|
@ -530,3 +530,41 @@ block:
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
surface.writeFile("tests/images/paths/arcTo3.png")
|
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))
|
||||||
|
|
Loading…
Reference in a new issue