commit
ee48d9cdd9
7 changed files with 200 additions and 63 deletions
|
@ -631,3 +631,19 @@ proc drawImage*(ctx: Context, image: Image, src, dest: Rect) =
|
|||
src.x, src.y, src.w, src.h,
|
||||
dest.x, dest.y, dest.w, dest.h
|
||||
)
|
||||
|
||||
proc arc*(ctx: Context, x, y, r, a0, a1: float32, ccw: bool = false) =
|
||||
## Draws a circular arc.
|
||||
ctx.path.arc(x, y, r, a0, a1, ccw)
|
||||
|
||||
proc arc*(ctx: Context, pos: Vec2, r: float32, a: Vec2, ccw: bool = false) =
|
||||
## Adds a circular arc to the current sub-path.
|
||||
ctx.path.arc(pos, r, a, ccw)
|
||||
|
||||
proc arcTo*(ctx: Context, x1, y1, x2, y2, radius: float32) =
|
||||
## Draws a circular arc using the given control points and radius.
|
||||
ctx.path.arcTo(x1, y1, x2, y2, radius)
|
||||
|
||||
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)
|
||||
|
|
|
@ -120,7 +120,7 @@ proc parsePath*(path: string): Path =
|
|||
try:
|
||||
numbers.add(parseFloat(path[numberStart ..< p]))
|
||||
except ValueError:
|
||||
raise newException(PixieError, "Invalid path, parsing paramter failed")
|
||||
raise newException(PixieError, "Invalid path, parsing parameter failed")
|
||||
numberStart = 0
|
||||
hitDecimal = false
|
||||
|
||||
|
@ -131,7 +131,7 @@ proc parsePath*(path: string): Path =
|
|||
let paramCount = parameterCount(kind)
|
||||
if paramCount == 0:
|
||||
if numbers.len != 0:
|
||||
raise newException(PixieError, "Invalid path, unexpected paramters")
|
||||
raise newException(PixieError, "Invalid path, unexpected parameters")
|
||||
result.commands.add(PathCommand(kind: kind))
|
||||
else:
|
||||
if numbers.len mod paramCount != 0:
|
||||
|
@ -383,67 +383,6 @@ proc quadraticCurveTo*(path: var Path, ctrl, to: Vec2) {.inline.} =
|
|||
## Bézier curve.
|
||||
path.quadraticCurveTo(ctrl.x, ctrl.y, to.x, to.y)
|
||||
|
||||
# proc arcTo*(path: var Path, ctrl1, ctrl2: Vec2, radius: float32) {.inline.} =
|
||||
# ## Adds a circular arc to the current sub-path, using the given control
|
||||
# ## points and radius.
|
||||
|
||||
# const epsilon = 1e-6.float32
|
||||
|
||||
# var radius = radius
|
||||
# if radius < 0:
|
||||
# radius = -radius
|
||||
|
||||
# if path.commands.len == 0:
|
||||
# path.moveTo(ctrl1)
|
||||
|
||||
# let
|
||||
# a = path.at - ctrl1
|
||||
# b = ctrl2 - ctrl1
|
||||
|
||||
# if a.lengthSq() < epsilon:
|
||||
# # If the control point is coincident with at, do nothing
|
||||
# discard
|
||||
# elif abs(a.y * b.x - a.x * b.y) < epsilon or radius == 0:
|
||||
# # If ctrl1, a and b are colinear or coincident or radius is zero
|
||||
# path.lineTo(ctrl1)
|
||||
# else:
|
||||
# let
|
||||
# c = ctrl2 - path.at
|
||||
# als = a.lengthSq()
|
||||
# bls = b.lengthSq()
|
||||
# cls = c.lengthSq()
|
||||
# al = a.length()
|
||||
# bl = b.length()
|
||||
# l = radius * tan((PI - arccos((als + bls - cls) / 2 * al * bl)) / 2)
|
||||
# ta = l / al
|
||||
# tb = l / bl
|
||||
|
||||
# if abs(ta - 1) > epsilon:
|
||||
# # If the start tangent is not coincident with path.at
|
||||
# path.lineTo(ctrl1 + a * ta)
|
||||
|
||||
# echo "INSIDE ", (als + bls - cls) / 2 * al * bl, " ", arccos((als + bls - cls) / 2 * al * bl)
|
||||
|
||||
# let to = ctrl1 + b * tb
|
||||
# path.commands.add(PathCommand(
|
||||
# kind: Arc,
|
||||
# numbers: @[
|
||||
# radius,
|
||||
# radius,
|
||||
# 0,
|
||||
# 0,
|
||||
# if a.y * c.x > a.x * c.y: 1 else: 0,
|
||||
# to.x,
|
||||
# to.y
|
||||
# ]
|
||||
# ))
|
||||
# path.at = to
|
||||
|
||||
# proc arcTo*(path: var Path, x1, y1, x2, y2, radius: float32) {.inline.} =
|
||||
# ## Adds a circular arc to the current sub-path, using the given control
|
||||
# ## points and radius.
|
||||
# path.arcTo(vec2(x1, y1), vec2(x2, y2), radius)
|
||||
|
||||
proc ellipticalArcTo*(
|
||||
path: var Path,
|
||||
rx, ry: float32,
|
||||
|
@ -461,6 +400,92 @@ proc ellipticalArcTo*(
|
|||
))
|
||||
path.at = vec2(x, y)
|
||||
|
||||
proc arc*(path: var Path, x, y, r, a0, a1: float32, ccw: bool) =
|
||||
## Adds a circular arc to the current sub-path.
|
||||
if r == 0: # When radius is zero, do nothing.
|
||||
return
|
||||
if r < 0: # When radius is negative, error.
|
||||
raise newException(PixieError, "Invalid arc, negative radius: " & $r)
|
||||
|
||||
let
|
||||
dx = r * cos(a0)
|
||||
dy = r * sin(a0)
|
||||
x0 = x + dx
|
||||
y0 = y + dy
|
||||
cw = not ccw
|
||||
|
||||
if path.commands.len == 0: # Is this path empty? Move to (x0, y0).
|
||||
path.moveTo(x0, y0)
|
||||
elif abs(path.at.x - x0) > epsilon or abs(path.at.y - y0) > epsilon:
|
||||
path.lineTo(x0, y0)
|
||||
|
||||
var angle =
|
||||
if ccw: a0 - a1
|
||||
else: a1 - a0
|
||||
if angle < 0:
|
||||
# When the angle goes the wrong way, flip the direction.
|
||||
angle = angle mod TAU + TAU
|
||||
|
||||
if angle > TAU - epsilon:
|
||||
# Angle describes a complete circle. Draw it in two arcs.
|
||||
path.ellipticalArcTo(r, r, 0, true, cw, x - dx, y - dy)
|
||||
path.at.x = x0
|
||||
path.at.y = y0
|
||||
path.ellipticalArcTo(r, r, 0, true, cw, path.at.x, path.at.y)
|
||||
elif angle > epsilon:
|
||||
path.at.x = x + r * cos(a1)
|
||||
path.at.y = y + r * sin(a1)
|
||||
path.ellipticalArcTo(r, r, 0, angle >= PI, cw, path.at.x, path.at.y)
|
||||
|
||||
proc arc*(path: var Path, pos: Vec2, r: float32, a: Vec2, ccw: bool = false) =
|
||||
## Adds a circular arc to the current sub-path.
|
||||
path.arc(pos.x, pos.y, r, a.x, a.y, ccw)
|
||||
|
||||
proc arcTo*(path: var Path, x1, y1, x2, y2, r: float32) =
|
||||
## Adds a circular arc using the given control points and radius.
|
||||
## Commonly used for making rounded corners.
|
||||
if r < 0: # When radius is negative, error.
|
||||
raise newException(PixieError, "Invalid arc, negative radius: " & $r)
|
||||
|
||||
let
|
||||
x0 = path.at.x
|
||||
y0 = path.at.y
|
||||
x21 = x2 - x1
|
||||
y21 = y2 - y1
|
||||
x01 = x0 - x1
|
||||
y01 = y0 - y1
|
||||
l01_2 = x01 * x01 + y01 * y01
|
||||
|
||||
if path.commands.len == 0: # Is this path empty? Move to (x0, y0).
|
||||
path.moveTo(x0, y0)
|
||||
elif not(l01_2 > epsilon): # Is (x1, y1) coincident with (x0, y0)? Do nothing.
|
||||
discard
|
||||
elif not(abs(y01 * x21 - y21 * x01) > epsilon) or r == 0: # Just a line?
|
||||
path.lineTo(x1, y1)
|
||||
else:
|
||||
let
|
||||
x20 = x2 - x0
|
||||
y20 = y2 - y0
|
||||
l21_2 = x21 * x21 + y21 * y21
|
||||
l20_2 = x20 * x20 + y20 * y20
|
||||
l21 = sqrt(l21_2)
|
||||
l01 = sqrt(l01_2)
|
||||
l = r * tan((PI - arccos((l21_2 + l01_2 - l20_2) / (2 * l21 * l01))) / 2)
|
||||
t01 = l / l01
|
||||
t21 = l / l21
|
||||
|
||||
# If the start tangent is not coincident with (x0, y0), line to.
|
||||
if abs(t01 - 1) > epsilon:
|
||||
path.lineTo(x1 + t01 * x01, y1 + t01 * y01)
|
||||
|
||||
path.at.x = x1 + t21 * x21
|
||||
path.at.y = y1 + t21 * y21
|
||||
path.ellipticalArcTo(r, r, 0, false, y01 * x20 > x01 * y20, path.at.x, path.at.y)
|
||||
|
||||
proc arcTo*(path: var Path, a, b: Vec2, r: float32) =
|
||||
## Adds a circular arc using the given control points and radius.
|
||||
path.arcTo(a.x, a.y, b.x, b.y, r)
|
||||
|
||||
proc rect*(path: var Path, x, y, w, h: float32, clockwise = true) =
|
||||
## Adds a rectangle.
|
||||
## Clockwise param can be used to subtract a rect from a path when using
|
||||
|
|
BIN
tests/images/paths/arc.png
Normal file
BIN
tests/images/paths/arc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
BIN
tests/images/paths/arcTo1.png
Normal file
BIN
tests/images/paths/arcTo1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
BIN
tests/images/paths/arcTo2.png
Normal file
BIN
tests/images/paths/arcTo2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
BIN
tests/images/paths/arcTo3.png
Normal file
BIN
tests/images/paths/arcTo3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
|
@ -434,3 +434,99 @@ block:
|
|||
mask.fillPath("M 10.1 10.1 H 60.1 V 60.1 H 10.1 z")
|
||||
mask.fillPath("M 30.1 30.1 H 80.1 V 80.1 H 30.1 z", blendMode = bmMask)
|
||||
writeFile("tests/images/paths/maskRectMaskAA.png", mask.encodePng())
|
||||
|
||||
block:
|
||||
var
|
||||
surface = newImage(256, 256)
|
||||
ctx = newContext(surface)
|
||||
|
||||
surface.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
# Draw shapes
|
||||
for i in 0 .. 3:
|
||||
for j in 0 .. 2:
|
||||
ctx.beginPath();
|
||||
let x = 25f + j.float32 * 50f; # x coordinate
|
||||
let y = 25f + i.float32 * 50f; # y coordinate
|
||||
let radius = 20f; # Arc radius
|
||||
let startAngle = 0f; # Starting point on circle
|
||||
let endAngle = PI + (PI * j.float32) / 2; # End point on circle
|
||||
let counterclockwise = i mod 2 == 1; # Draw counterclockwise
|
||||
|
||||
ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);
|
||||
|
||||
if i > 1:
|
||||
ctx.fill();
|
||||
else:
|
||||
ctx.stroke();
|
||||
|
||||
surface.writeFile("tests/images/paths/arc.png")
|
||||
|
||||
block:
|
||||
var
|
||||
surface = newImage(256, 256)
|
||||
ctx = newContext(surface)
|
||||
surface.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
let
|
||||
p0 = vec2(230, 20 )
|
||||
p1 = vec2(90, 130)
|
||||
p2 = vec2(20, 20 )
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(p0.x, p0.y);
|
||||
ctx.arcTo(p1.x, p1.y, p2.x, p2.y, 50);
|
||||
ctx.lineTo(p2.x, p2.y);
|
||||
ctx.stroke()
|
||||
|
||||
surface.writeFile("tests/images/paths/arcTo1.png")
|
||||
|
||||
block:
|
||||
var
|
||||
surface = newImage(256, 256)
|
||||
ctx = newContext(surface)
|
||||
surface.fill(rgba(255, 255, 255, 255))
|
||||
# Tangential lines
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = "gray";
|
||||
ctx.moveTo(200, 20);
|
||||
ctx.lineTo(200, 130);
|
||||
ctx.lineTo(50, 20);
|
||||
ctx.stroke();
|
||||
|
||||
# Arc
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = "black";
|
||||
ctx.lineWidth = 5;
|
||||
ctx.moveTo(200, 20);
|
||||
ctx.arcTo(200,130, 50,20, 40);
|
||||
ctx.stroke();
|
||||
|
||||
# Start point
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = "blue";
|
||||
ctx.arc(200, 20, 5, 0, 2 * PI);
|
||||
ctx.fill();
|
||||
|
||||
# Control points
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = "red";
|
||||
ctx.arc(200, 130, 5, 0, 2 * PI); # Control point one
|
||||
ctx.arc(50, 20, 5, 0, 2 * PI); # Control point two
|
||||
ctx.fill();
|
||||
|
||||
surface.writeFile("tests/images/paths/arcTo2.png")
|
||||
|
||||
block:
|
||||
var
|
||||
surface = newImage(256, 256)
|
||||
ctx = newContext(surface)
|
||||
surface.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(180, 90);
|
||||
ctx.arcTo(180,130, 110,130, 130);
|
||||
ctx.lineTo(110, 130);
|
||||
ctx.stroke();
|
||||
|
||||
surface.writeFile("tests/images/paths/arcTo3.png")
|
||||
|
|
Loading…
Reference in a new issue