From bd46136952ee05faddc53eeba54ad548c7a0674d Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 10 Jul 2021 23:00:01 -0700 Subject: [PATCH 1/2] Add arcs. --- src/pixie/contexts.nim | 8 ++ src/pixie/paths.nim | 143 +++++++++++++++++++--------------- tests/images/paths/arc.png | Bin 0 -> 8477 bytes tests/images/paths/arcTo1.png | Bin 0 -> 3670 bytes tests/images/paths/arcTo2.png | Bin 0 -> 4288 bytes tests/images/paths/arcTo3.png | Bin 0 -> 3740 bytes tests/test_paths.nim | 96 +++++++++++++++++++++++ 7 files changed, 184 insertions(+), 63 deletions(-) create mode 100644 tests/images/paths/arc.png create mode 100644 tests/images/paths/arcTo1.png create mode 100644 tests/images/paths/arcTo2.png create mode 100644 tests/images/paths/arcTo3.png diff --git a/src/pixie/contexts.nim b/src/pixie/contexts.nim index 503674d..a1d6807 100644 --- a/src/pixie/contexts.nim +++ b/src/pixie/contexts.nim @@ -631,3 +631,11 @@ 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 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) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index ba04f0c..d8399d0 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -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,84 @@ 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 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 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 diff --git a/tests/images/paths/arc.png b/tests/images/paths/arc.png new file mode 100644 index 0000000000000000000000000000000000000000..76b3cb020d3ba9de60cd2c4c11862f57ddb033be GIT binary patch literal 8477 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 literal 0 HcmV?d00001 diff --git a/tests/images/paths/arcTo1.png b/tests/images/paths/arcTo1.png new file mode 100644 index 0000000000000000000000000000000000000000..c4345beb0e81da8631f3fcef1c62ee2a086a3489 GIT binary patch literal 3670 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 literal 0 HcmV?d00001 diff --git a/tests/images/paths/arcTo3.png b/tests/images/paths/arcTo3.png new file mode 100644 index 0000000000000000000000000000000000000000..e7674af21630df277b63c178e8cc4f227aba26f5 GIT binary patch literal 3740 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 E0j~sx3;+NC literal 0 HcmV?d00001 diff --git a/tests/test_paths.nim b/tests/test_paths.nim index e3ab5ec..ec0096e 100644 --- a/tests/test_paths.nim +++ b/tests/test_paths.nim @@ -435,3 +435,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") From 48694ad73537dcfde804f97e9152da4cdc933516 Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 10 Jul 2021 23:31:25 -0700 Subject: [PATCH 2/2] Add vector overloads of arc and arcTo. --- src/pixie/contexts.nim | 8 ++++++++ src/pixie/paths.nim | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/pixie/contexts.nim b/src/pixie/contexts.nim index a1d6807..3e73989 100644 --- a/src/pixie/contexts.nim +++ b/src/pixie/contexts.nim @@ -636,6 +636,14 @@ 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) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index d8399d0..ff28af9 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -437,6 +437,10 @@ proc arc*(path: var Path, x, y, r, a0, a1: float32, ccw: bool) = 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. @@ -478,6 +482,10 @@ proc arcTo*(path: var Path, x1, y1, x2, y2, r: float32) = 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