Created
June 9, 2021 07:23
-
-
Save YogeshPateliOS/c400e3eda63951cad407720cbfaf9f3a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Cocoa | |
final class Potrace { | |
class Point<T> { | |
var x: T | |
var y: T | |
init(x: T, y: T) { | |
self.x = x | |
self.y = y | |
} | |
func copy() -> Point<T> { | |
return Point<T>(x: self.x, y: self.y) | |
} | |
static func toPointF(point: PointI) -> PointF { | |
return PointF(x: Double(point.x), y: Double(point.y)) | |
} | |
} | |
typealias PointF = Point<Double> | |
typealias PointI = Point<Int> | |
struct Bitmap { | |
var w: Int | |
var h: Int | |
var size: Int | |
var data: [UInt8] | |
init(width: Int, height: Int) { | |
self.w = width | |
self.h = height | |
self.size = width * height | |
self.data = [UInt8](repeating: 0, count: self.size) | |
} | |
func at(x: Int, y: Int) -> Bool { | |
return (x >= 0 && x < self.w && y >= 0 && y < self.h) && | |
data[self.w * y + x] == 1 | |
} | |
func index(i: Int) -> PointI { | |
let point = PointI(x: 0, y: 0) | |
point.y = Int(floor(Double(i / self.w))) | |
point.x = i - point.y * self.w | |
return point | |
} | |
mutating func flip(x: Int, y: Int) { | |
if self.at(x: x, y: y) { | |
data[self.w * y + x] = 0 | |
} else { | |
data[self.w * y + x] = 1 | |
} | |
} | |
func copy() -> Bitmap { | |
var bm = Bitmap(width: w, height: h) | |
for (index, value) in data.enumerated() { | |
bm.data[index] = value | |
} | |
return bm | |
} | |
} | |
class Curve { | |
var n: Int | |
var tag: [String] | |
var c: BoxedArray<PointF> | |
var alphacurve: Int | |
var vertex: BoxedArray<PointF> | |
var alpha: [Double] | |
var alpha0: [Double] | |
var beta: [Double] | |
init(n: Int) { | |
self.n = n | |
self.tag = [String](repeating: "", count: n) | |
self.c = BoxedArray(repeating: Point(x: 0.0, y: 0.0), count: n * 3) | |
self.alphacurve = 0 | |
self.vertex = BoxedArray(repeating: Point(x: 0.0, y: 0.0), count: n) | |
self.alpha = [Double](repeating: 0.0, count: n) | |
self.alpha0 = [Double](repeating: 0.0, count: n) | |
self.beta = [Double](repeating: 0.0, count: n) | |
} | |
} | |
struct Sum { | |
var x: Int | |
var y: Int | |
var xy: Int | |
var x2: Int | |
var y2: Int | |
} | |
class Path { | |
var area: Int | |
var len: Int | |
var curve: Curve | |
var pt: BoxedArray<PointI> | |
var minX: Int | |
var minY: Int | |
var maxX: Int | |
var maxY: Int | |
var sign: String | |
var sums: BoxedArray<Sum> | |
var lon: [Int] | |
var po: [Int] | |
var m: Int | |
var x0: Int | |
var y0: Int | |
init() { | |
self.area = 0 | |
self.len = 0 | |
self.curve = Curve(n: 0) | |
self.pt = BoxedArray() | |
self.minX = 100000 | |
self.minY = 100000 | |
self.maxX = -1 | |
self.maxY = -1 | |
self.sign = "" | |
self.sums = BoxedArray() | |
self.lon = [] | |
self.po = [] | |
self.m = 0 | |
self.x0 = 0 | |
self.y0 = 0 | |
} | |
} | |
/* | |
* Potrace.Settings | |
* | |
* turnpolicy ("black" / "white" / "left" / "right" / "minority" / "majority") | |
* how to resolve ambiguities in path decomposition. (default: "minority") | |
* | |
* turdsize | |
* suppress speckles of up to this size (default: 2) | |
* | |
* optcurve (true / false) | |
* turn on/off curve optimization (default: true) | |
* | |
* alphamax | |
* corner threshold parameter (default: 1) | |
* | |
* opttolerance | |
* curve optimization tolerance (default: 0.2) | |
*/ | |
public struct Settings { | |
var turnpolicy: String | |
var turdsize: Int | |
var optcurve: Bool | |
var alphamax: Double | |
var opttolerance: Double | |
init(turnPolicy: String = "minority", | |
turdSize: Int = 2, | |
optCurve: Bool = true, | |
alphaMax: Double = 1.0, | |
optTolerance: Double = 0.2) { | |
self.turnpolicy = turnPolicy | |
self.turdsize = turdSize | |
self.optcurve = optCurve | |
self.alphamax = alphaMax | |
self.opttolerance = optTolerance | |
} | |
} | |
fileprivate var bm: Bitmap! | |
fileprivate var info: Settings! | |
fileprivate var pathlist = [Path]() | |
init(data: UnsafeMutableRawPointer, width: Int, height: Int) { | |
let pixelData = data.assumingMemoryBound(to: UInt8.self) | |
bm = Bitmap(width: width, height: height) | |
var j = 0, i = 0 | |
for _ in 0..<bm.data.count { | |
let val = 0.2126 * Double(pixelData[i]) + 0.7153 * Double(pixelData[i + 1]) + | |
0.0721 * Double(pixelData[i + 2]) | |
bm.data[j] = val < 128 ? 1 : 0 | |
i += 4 | |
j += 1 | |
} | |
} | |
func process(settings: Settings = Settings()) { | |
self.info = settings | |
bmToPathList() | |
processPath() | |
} | |
func bmToPathList() { | |
var bm1 = bm.copy() | |
var currentPoint = Point(x: 0, y: 0) | |
var path: Path | |
func findNext(point: PointI) -> PointI? { | |
var i = bm1.w * point.y + point.x | |
while i < bm1.size && bm1.data[i] != 1 { | |
i += 1 | |
} | |
if i < bm1.size { | |
return bm1.index(i: i) | |
} else { | |
return nil | |
} | |
} | |
func majority(x: Int, y: Int) -> Int { | |
for i in 2..<5 { | |
var ct = 0 | |
for a in (-i + 1)...(i - 1) { | |
ct += bm1.at(x: x + a, y: y + i - 1) ? 1 : -1 | |
ct += bm1.at(x: x + i - 1, y: y + a - 1) ? 1 : -1 | |
ct += bm1.at(x: x + a - 1, y: y - i) ? 1 : -1 | |
ct += bm1.at(x: x - i, y: y + a) ? 1 : -1 | |
} | |
return ct > 0 ? 1 : 0 | |
} | |
return 0 | |
} | |
func findPath(point: PointI) -> Path { | |
var path = Path(), | |
x = point.x, y = point.y, | |
dirx = 0, diry = 1, tmp: Int | |
path.sign = bm.at(x: point.x, y: point.y) ? "+" : "-" | |
while true { | |
path.pt.append(Point(x: x, y: y)) | |
if x > path.maxX { | |
path.maxX = x | |
} | |
if x < path.minX { | |
path.minX = x | |
} | |
if y > path.maxY { | |
path.maxY = y | |
} | |
if y < path.minY { | |
path.minY = y | |
} | |
path.len += 1 | |
x += dirx | |
y += diry | |
path.area -= x * diry | |
if x == point.x && y == point.y { | |
break | |
} | |
let l = bm1.at(x: x + (dirx + diry - 1 ) / 2, y: y + (diry - dirx - 1) / 2) | |
let r = bm1.at(x: x + (dirx - diry - 1) / 2, y: y + (diry + dirx - 1) / 2) | |
if r && !l { | |
if info.turnpolicy == "right" || | |
(info.turnpolicy == "black" && path.sign == "+") || | |
(info.turnpolicy == "white" && path.sign == "-") || | |
(info.turnpolicy == "majority" && majority(x: x, y: y) != 0) || | |
(info.turnpolicy == "minority" && majority(x: x, y: y) == 0) { | |
tmp = dirx | |
dirx = -diry | |
diry = tmp | |
} else { | |
tmp = dirx | |
dirx = diry | |
diry = -tmp | |
} | |
} else if r { | |
tmp = dirx | |
dirx = -diry | |
diry = tmp | |
} else if !l { | |
tmp = dirx | |
dirx = diry | |
diry = -tmp | |
} | |
} | |
return path | |
} | |
func xorPath(path: Path) { | |
var y1 = path.pt[0].y, | |
len = path.len, | |
x: Int, y: Int, maxX: Int, minY: Int | |
for i in 1..<len { | |
x = path.pt[i].x | |
y = path.pt[i].y | |
if y != y1 { | |
minY = y1 < y ? y1 : y | |
maxX = path.maxX | |
for j in x..<maxX { | |
bm1.flip(x: j, y: minY) | |
} | |
y1 = y | |
} | |
} | |
} | |
while let currentPoint = findNext(point: currentPoint) { | |
path = findPath(point: currentPoint) | |
xorPath(path: path) | |
if path.area > info.turdsize { | |
pathlist.append(path) | |
} | |
} | |
} | |
// | |
// processPath | |
// | |
func processPath() { | |
struct Quad { | |
var data: [Double] | |
init() { | |
self.data = [Double](repeating: 0.0, count: 9) | |
} | |
func at(x: Int, y: Int) -> Double { | |
return self.data[x * 3 + y] | |
} | |
} | |
func mod(a: Int, n: Int) -> Int { | |
return a >= n ? a % n : a>=0 ? a : n-1-(-1-a) % n | |
} | |
func xprod(p1: PointI, p2: PointI) -> Int { | |
return p1.x * p2.y - p1.y * p2.x | |
} | |
func cyclic(a: Int, b: Int, c: Int) -> Bool { | |
if a <= c { | |
return (a <= b && b < c) | |
} else { | |
return (a <= b || b < c) | |
} | |
} | |
func sign(i: Int) -> Int { | |
return i > 0 ? 1 : i < 0 ? -1 : 0 | |
} | |
func quadform(Q: Quad, w: PointF) -> Double { | |
var v = [Double](repeating: 0.0, count: 3) | |
var sum: Double = 0.0 | |
v[0] = w.x | |
v[1] = w.y | |
v[2] = 1 | |
for i in 0..<3 { | |
for j in 0..<3 { | |
sum += Double(v[i]) * Q.at(x: i, y: j) * Double(v[j]) | |
} | |
} | |
return sum | |
} | |
func interval(lambda: Double, a: PointF, b: PointF) -> PointF { | |
let res = PointF(x: 0, y: 0) | |
res.x = a.x + lambda * (b.x - a.x) | |
res.y = a.y + lambda * (b.y - a.y) | |
return res | |
} | |
func dorth_infty(p0: PointF, p2: PointF) -> PointI { | |
let r = PointI(x: 0, y: 0) | |
r.y = sign(i: Int(p2.x - p0.x)) | |
r.x = -sign(i: Int(p2.y - p0.y)) | |
return r | |
} | |
func ddenom(p0: PointF, p2: PointF) -> Double { | |
let r = dorth_infty(p0: p0, p2: p2) | |
return Double(r.y) * (p2.x - p0.x) - Double(r.x) * (p2.y - p0.y) | |
} | |
func dpara(p0: PointF, p1: PointF, p2: PointF) -> Double { | |
let x1 = p1.x - p0.x | |
let y1 = p1.y - p0.y | |
let x2 = p2.x - p0.x | |
let y2 = p2.y - p0.y | |
return x1 * y2 - x2 * y1 | |
} | |
func cprod(p0: PointF, p1: PointF, p2: PointF, p3: PointF) -> Double { | |
let x1 = p1.x - p0.x | |
let y1 = p1.y - p0.y | |
let x2 = p3.x - p2.x | |
let y2 = p3.y - p2.y | |
return x1 * y2 - x2 * y1 | |
} | |
func iprod(p0: PointF, p1: PointF, p2: PointF) -> Double { | |
let x1 = p1.x - p0.x | |
let y1 = p1.y - p0.y | |
let x2 = p2.x - p0.x | |
let y2 = p2.y - p0.y | |
return x1*x2 + y1*y2 | |
} | |
func iprod1(p0: PointF, p1: PointF, p2: PointF, p3: PointF) -> Double { | |
let x1 = p1.x - p0.x | |
let y1 = p1.y - p0.y | |
let x2 = p3.x - p2.x | |
let y2 = p3.y - p2.y | |
return x1 * x2 + y1 * y2 | |
} | |
func ddist(p: PointF, q: PointF) -> Double { | |
let part1 = (p.x - q.x) * (p.x - q.x) | |
let part2 = (p.y - q.y) * (p.y - q.y) | |
return sqrt(part1 + part2) | |
} | |
func bezier(t: Double, p0: PointF, p1: PointF, p2: PointF, p3: PointF) -> PointF { | |
let s = 1.0 - t | |
let res = PointF(x: 0, y: 0) | |
let part1 = s*s*s*p0.x | |
let part11 = 3*(s*s*t)*p1.x | |
let part2 = 3*(t*t*s)*p2.x | |
let part22 = t*t*t*p3.x | |
let part3 = s*s*s*p0.y | |
let part33 = 3*(s*s*t)*p1.y | |
let part4 = 3*(t*t*s)*p2.y | |
let part44 = t*t*t*p3.y | |
res.x = part1 + part11 + part2 + part22 | |
res.y = part3 + part33 + part4 + part44 | |
return res | |
} | |
func tangent(p0: PointF, p1: PointF, p2: PointF, p3: PointF, q0: PointF, q1: PointF) -> Double { | |
var s: Double, r1: Double, r2: Double | |
let A = cprod(p0: p0, p1: p1, p2: q0, p3: q1) | |
let B = cprod(p0: p1, p1: p2, p2: q0, p3: q1) | |
let C = cprod(p0: p2, p1: p3, p2: q0, p3: q1) | |
let a = A - 2 * B + C | |
let b = -2 * A + 2 * B | |
let c = A | |
let d = b * b - 4 * a * c | |
if a == 0 || d < 0 { | |
return -1.0 | |
} | |
s = sqrt(d) | |
r1 = (-b + s) / (2 * a) | |
r2 = (-b - s) / (2 * a) | |
if r1 >= 0 && r1 <= 1 { | |
return r1 | |
} else if r2 >= 0 && r2 <= 1 { | |
return r2 | |
} else { | |
return -1.0 | |
} | |
} | |
func calcSums(path: Path) { | |
var x: Int, y: Int | |
path.x0 = path.pt[0].x | |
path.y0 = path.pt[0].y | |
path.sums = BoxedArray() | |
let s = path.sums | |
s.append(Sum(x: 0, y: 0, xy: 0, x2: 0, y2: 0)) | |
for i in 0 ..< path.len { | |
x = path.pt[i].x - path.x0 | |
y = path.pt[i].y - path.y0 | |
s.append(Sum(x: s[i].x + x, y: s[i].y + y, xy: s[i].xy + x * y, | |
x2: s[i].x2 + x * x, y2: s[i].y2 + y * y)) | |
} | |
} | |
func calcLon(path: Path) { | |
var n = path.len, pt = path.pt, dir: Int, | |
pivk = [Int](repeating: 0, count: n), | |
nc = [Int](repeating: 0, count: n), | |
ct = [Int](repeating: 0, count: 4) | |
path.lon = [Int](repeating: 0, count: n) | |
var constraint = [PointI(x: 0, y: 0), PointI(x: 0, y: 0)], | |
cur = PointI(x: 0, y: 0), | |
off = PointI(x: 0, y: 0), | |
dk = PointI(x: 0, y: 0), | |
foundk: Int = 0 | |
var i: Int, j: Int, k1: Int, a: Int, b: Int, c: Int, d: Int, k: Int = 0 | |
for i in (0...n-1).reversed() { | |
if pt[i].x != pt[k].x && pt[i].y != pt[k].y { | |
k = i + 1 | |
} | |
nc[i] = k | |
} | |
for i in (0...n-1).reversed() { | |
ct[0] = 0 | |
ct[1] = 0 | |
ct[2] = 0 | |
ct[3] = 0 | |
dir = (3 + 3 * (pt[mod(a: i + 1, n: n)].x - pt[i].x) + | |
(pt[mod(a: i + 1, n: n)].y - pt[i].y)) / 2 | |
ct[dir] += 1 | |
constraint[0].x = 0 | |
constraint[0].y = 0 | |
constraint[1].x = 0 | |
constraint[1].y = 0 | |
k = nc[i] | |
k1 = i | |
while true { | |
foundk = 0 | |
dir = (3 + 3 * sign(i: pt[k].x - pt[k1].x) + | |
sign(i: pt[k].y - pt[k1].y)) / 2 | |
ct[dir] += 1 | |
if ct[0] != 0 && ct[1] != 0 && ct[2] != 0 && ct[3] != 0 { | |
pivk[i] = k1 | |
foundk = 1 | |
break | |
} | |
cur.x = pt[k].x - pt[i].x | |
cur.y = pt[k].y - pt[i].y | |
if xprod(p1: constraint[0], p2: cur) < 0 || xprod(p1: constraint[1], p2: cur) > 0 { | |
break | |
} | |
if abs(cur.x) <= 1 && abs(cur.y) <= 1 { | |
// NOOP | |
} else { | |
off.x = cur.x + ((cur.y >= 0 && (cur.y > 0 || cur.x < 0)) ? 1 : -1) | |
off.y = cur.y + ((cur.x <= 0 && (cur.x < 0 || cur.y < 0)) ? 1 : -1) | |
if xprod(p1: constraint[0], p2: off) >= 0 { | |
constraint[0].x = off.x | |
constraint[0].y = off.y | |
} | |
off.x = cur.x + ((cur.y <= 0 && (cur.y < 0 || cur.x < 0)) ? 1 : -1) | |
off.y = cur.y + ((cur.x >= 0 && (cur.x > 0 || cur.y < 0)) ? 1 : -1) | |
if xprod(p1: constraint[1], p2: off) <= 0 { | |
constraint[1].x = off.x | |
constraint[1].y = off.y | |
} | |
} | |
k1 = k | |
k = nc[k1] | |
if (!cyclic(a: k, b: i, c: k1)) { | |
break | |
} | |
} | |
if foundk == 0 { | |
dk.x = sign(i: pt[k].x-pt[k1].x) | |
dk.y = sign(i: pt[k].y-pt[k1].y) | |
cur.x = pt[k1].x - pt[i].x | |
cur.y = pt[k1].y - pt[i].y | |
a = xprod(p1: constraint[0], p2: cur) | |
b = xprod(p1: constraint[0], p2: dk) | |
c = xprod(p1: constraint[1], p2: cur) | |
d = xprod(p1: constraint[1], p2: dk) | |
j = 10000000 | |
if b < 0 { | |
j = Int(floor(Double(a / -b))) | |
} | |
if d > 0 { | |
j = min(j, Int(floor(Double(-c / d)))) | |
} | |
pivk[i] = mod(a: k1+j, n: n) | |
} | |
} | |
j=pivk[n-1] | |
path.lon[n-1]=j | |
for i in (0...n-2).reversed() { | |
if cyclic(a: i+1, b: pivk[i], c: j) { | |
j=pivk[i] | |
} | |
path.lon[i]=j | |
} | |
i = n - 1 | |
while cyclic(a: mod(a: i+1, n: n), b: j, c: path.lon[i]) { | |
i -= 1 | |
} | |
} | |
func bestPolygon(path: Path) { | |
func penalty3(path: Path, i: Int, jj: Int) -> Double { | |
let n = path.len, pt = path.pt | |
let sums = path.sums | |
var x: Int, y: Int, xy: Int, x2: Int, y2: Int, | |
k: Int, a: Double, b: Double, c: Double, s: Double, | |
px: Double, py: Double, ex: Int, ey: Int, | |
r: Int = 0 | |
var j = jj | |
if j >= n { | |
j -= n | |
r = 1 | |
} | |
if r == 0 { | |
x = sums[j+1].x - sums[i].x | |
y = sums[j+1].y - sums[i].y | |
x2 = sums[j+1].x2 - sums[i].x2 | |
xy = sums[j+1].xy - sums[i].xy | |
y2 = sums[j+1].y2 - sums[i].y2 | |
k = j+1 - i | |
} else { | |
x = sums[j+1].x - sums[i].x + sums[n].x | |
y = sums[j+1].y - sums[i].y + sums[n].y | |
x2 = sums[j+1].x2 - sums[i].x2 + sums[n].x2 | |
xy = sums[j+1].xy - sums[i].xy + sums[n].xy | |
y2 = sums[j+1].y2 - sums[i].y2 + sums[n].y2 | |
k = j+1 - i + n | |
} | |
px = (Double(pt[i].x + pt[j].x) / 2.0) - Double(pt[0].x) | |
py = (Double(pt[i].y + pt[j].y) / 2.0) - Double(pt[0].y) | |
ey = (pt[j].x - pt[i].x) | |
ex = -(pt[j].y - pt[i].y) | |
let a1 = (Double(x2) - Double(2*x)*px) | |
let a2 = Double(k) + px*px | |
a = a1 / a2 | |
let b1 = (Double(xy) - Double(x)*py - Double(y)*px) | |
let b2 = Double(k) + px*py | |
b = b1 / b2 | |
let c1 = (Double(y2) - Double(2*y)*py) | |
let c2 = Double(k) + py*py | |
c = c1 / c2 | |
let s1 = Double(ex * ex) * a | |
let s2 = Double(2 * ex * ey) * b | |
let s3 = Double(ey * ey) * c | |
s = s1 + s2 + s3 | |
return sqrt(s) | |
} | |
var i: Int, j: Int, m: Int, | |
n = path.len, | |
pen = [Double](repeating: 0.0, count: n + 1), | |
prev = [Int](repeating: 0, count: n + 1), | |
clip0 = [Int](repeating: 0, count: n), | |
clip1 = [Int](repeating: 0, count: n + 1), | |
seg0 = [Int](repeating: 0, count: n + 1), | |
seg1 = [Int](repeating: 0, count: n + 1), | |
thispen: Double, best: Double, c: Int | |
for i in 0..<n { | |
c = mod(a: path.lon[mod(a: i-1, n: n)]-1, n: n) | |
if c == i { | |
c = mod(a: i+1, n: n) | |
} | |
if c < i { | |
clip0[i] = n | |
} else { | |
clip0[i] = c | |
} | |
} | |
j = 1 | |
for i in 0..<n { | |
while j <= clip0[i] { | |
clip1[j] = i | |
j += 1 | |
} | |
} | |
i = 0 | |
j = 0 | |
while i < n { | |
seg0[j] = i | |
i = clip0[i] | |
j += 1 | |
} | |
seg0[j] = n | |
m = j | |
i = n | |
j = m | |
while j > 0 { | |
seg1[j] = i | |
i = clip1[i] | |
j -= 1 | |
} | |
seg1[0] = 0 | |
pen[0]=0 | |
j = 1 | |
while j <= m { | |
for i in seg1[j]...seg0[j] { | |
best = -1 | |
for k in (clip1[i]...seg0[j-1]).reversed() { | |
thispen = penalty3(path: path, i: k, jj: i) + pen[k] | |
if best < 0 || thispen < best { | |
prev[i] = k | |
best = thispen | |
} | |
} | |
pen[i] = best | |
} | |
j += 1 | |
} | |
path.m = m | |
path.po = [Int](repeating: 0, count: m) | |
i = n | |
j = m - 1 | |
while i > 0 { | |
i = prev[i] | |
path.po[j] = i | |
j -= 1 | |
} | |
} | |
func adjustVertices(path: Path) { | |
func pointslope(path: Path, ii: Int, jj: Int, ctr: PointF, dir: PointF) { | |
let n = path.len | |
let sums = path.sums | |
var x: Int, y: Int, xy: Int, x2: Int, y2: Int, | |
k: Int, a: Double, b: Double, c: Double, lambda2: Double, | |
l: Double, | |
r: Int = 0 | |
var i = ii | |
var j = jj | |
while j>=n { | |
j-=n | |
r+=1 | |
} | |
while i>=n { | |
i-=n | |
r-=1 | |
} | |
while j<0 { | |
j+=n | |
r-=1 | |
} | |
while i<0 { | |
i+=n | |
r+=1 | |
} | |
x = sums[j+1].x-sums[i].x+r*sums[n].x | |
y = sums[j+1].y-sums[i].y+r*sums[n].y | |
x2 = sums[j+1].x2-sums[i].x2+r*sums[n].x2 | |
xy = sums[j+1].xy-sums[i].xy+r*sums[n].xy | |
y2 = sums[j+1].y2-sums[i].y2+r*sums[n].y2 | |
k = j+1-i+r*n | |
ctr.x = Double(x/k) | |
ctr.y = Double(y/k) | |
a = Double((x2-x*x/k)/k) | |
b = Double((xy-x*y/k)/k) | |
c = Double((y2-y*y/k)/k) | |
lambda2 = (a+c+sqrt((a-c)*(a-c)+4*b*b))/2 | |
a -= lambda2 | |
c -= lambda2 | |
if abs(a) >= abs(c) { | |
l = sqrt(a*a+b*b) | |
if l != 0 { | |
dir.x = -b/l | |
dir.y = a/l | |
} | |
} else { | |
l = sqrt(c*c+b*b) | |
if l != 0 { | |
dir.x = -c/l | |
dir.y = b/l | |
} | |
} | |
if l == 0 { | |
dir.x = 0.0 | |
dir.y = 0.0 | |
} | |
} | |
var m = path.m, po = path.po, n = path.len, pt = path.pt, | |
x0 = path.x0, y0 = path.y0, | |
ctr = [PointF](repeating: PointF(x: 0, y: 0), count: m), | |
dir = [PointF](repeating: PointF(x: 0, y: 0), count: m), | |
q = [Quad](repeating: Quad(), count: m), | |
v = [Double](repeating: 0.0, count: m), d: Double, j: Int, | |
s = PointI(x: 0, y: 0) | |
path.curve = Curve(n: m) | |
for i in 0..<m { | |
j = po[mod(a: i+1, n: m)] | |
j = mod(a: j-po[i], n: n)+po[i] | |
ctr[i] = PointF(x: 0, y: 0) | |
dir[i] = PointF(x: 0, y: 0) | |
pointslope(path: path, ii: po[i], jj: j, ctr: ctr[i], dir: dir[i]) | |
} | |
for i in 0..<m { | |
q[i] = Quad() | |
d = dir[i].x * dir[i].x + dir[i].y * dir[i].y | |
if d == 0.0 { | |
for j in 0..<3 { | |
for k in 0..<3 { | |
q[i].data[j * 3 + k] = 0 | |
} | |
} | |
} else { | |
v[0] = dir[i].y | |
v[1] = -dir[i].x | |
v[2] = -v[1] * ctr[i].y - v[0] * ctr[i].x | |
for l in 0..<3 { | |
for k in 0..<3 { | |
q[i].data[l * 3 + k] = v[l] * v[k] / d | |
} | |
} | |
} | |
} | |
var Q: Quad, w: PointF, dx: Double, dy: Double, | |
det: Double, min: Double, cand: Double, xmin: Double, ymin: Double | |
for i in 0..<m { | |
Q = Quad() | |
w = PointF(x: 0, y: 0) | |
s.x = pt[po[i]].x-x0 | |
s.y = pt[po[i]].y-y0 | |
j = mod(a: i-1, n: m) | |
for l in 0..<3 { | |
for k in 0..<3 { | |
Q.data[l * 3 + k] = q[j].at(x: l, y: k) + q[i].at(x: l, y: k) | |
} | |
} | |
while true { | |
det = Q.at(x: 0, y: 0)*Q.at(x: 1, y: 1) - Q.at(x: 0, y: 1)*Q.at(x: 1, y: 0) | |
if det != 0.0 { | |
w.x = (-Q.at(x: 0, y: 2)*Q.at(x: 1, y: 1) + Q.at(x: 1, y: 2)*Q.at(x: 0, y: 1)) / det | |
w.y = ( Q.at(x: 0, y: 2)*Q.at(x: 1, y: 0) - Q.at(x: 1, y: 2)*Q.at(x: 0, y: 0)) / det | |
break | |
} | |
if (Q.at(x: 0, y: 0) > Q.at(x: 1, y: 1)) { | |
v[0] = -Q.at(x: 0, y: 1) | |
v[1] = Q.at(x: 0, y: 0) | |
} else if (Q.at(x: 1, y: 1) != 0) { | |
v[0] = -Q.at(x: 1, y: 1) | |
v[1] = Q.at(x: 1, y: 0) | |
} else { | |
v[0] = 1 | |
v[1] = 0 | |
} | |
d = v[0] * v[0] + v[1] * v[1] | |
v[2] = -v[1] * Double(s.y) - v[0] * Double(s.x) | |
for l in 0..<3 { | |
for k in 0..<3 { | |
Q.data[l * 3 + k] += v[l] * v[k] / d | |
} | |
} | |
} | |
dx = abs(w.x-Double(s.x)) | |
dy = abs(w.y-Double(s.y)) | |
if dx <= 0.5 && dy <= 0.5 { | |
path.curve.vertex[i] = PointF(x: w.x+Double(x0), y: w.y+Double(y0)) | |
continue | |
} | |
min = quadform(Q: Q, w: PointI.toPointF(point: s)) | |
xmin = Double(s.x) | |
ymin = Double(s.y) | |
if (Q.at(x: 0, y: 0) != 0.0) { | |
for z in 0..<2 { | |
w.y = Double(s.y)-0.5+Double(z) | |
w.x = -(Q.at(x: 0, y: 1) * w.y + Q.at(x: 0, y: 2)) / Q.at(x: 0, y: 0) | |
dx = abs(w.x-Double(s.x)) | |
cand = quadform(Q: Q, w: w) | |
if dx <= 0.5 && cand < min { | |
min = cand | |
xmin = w.x | |
ymin = w.y | |
} | |
} | |
} | |
if (Q.at(x: 1, y: 1) != 0.0) { | |
for z in 0..<2 { | |
w.x = Double(s.x)-0.5+Double(z) | |
w.y = -(Q.at(x: 1, y: 0) * w.x + Q.at(x: 1, y: 2)) / Q.at(x: 1, y: 1) | |
dy = abs(w.y-Double(s.y)) | |
cand = quadform(Q: Q, w: w) | |
if dy <= 0.5 && cand < min { | |
min = cand | |
xmin = w.x | |
ymin = w.y | |
} | |
} | |
} | |
for l in 0..<3 { | |
for k in 0..<3 { | |
w.x = Double(s.x)-0.5+Double(l) | |
w.y = Double(s.y)-0.5+Double(k) | |
cand = quadform(Q: Q, w: w) | |
if cand < min { | |
min = cand | |
xmin = w.x | |
ymin = w.y | |
} | |
} | |
} | |
path.curve.vertex[i] = PointF(x: xmin + Double(x0), y: ymin + Double(y0)) | |
} | |
} | |
func reverse(path: Path) { | |
var curve = path.curve, m = curve.n, v = curve.vertex, | |
i: Int, j: Int, tmp: PointF | |
i = 0 | |
j = m - 1 | |
while i < j { | |
tmp = v[i] | |
v[i] = v[j] | |
v[j] = tmp | |
i += 1 | |
j -= 1 | |
} | |
} | |
func smooth(path: Path) { | |
let m = path.curve.n, curve = path.curve | |
var j: Int, k: Int, dd: Double, denom: Double, alpha: Double, | |
p2: PointF, p3: PointF, p4: PointF | |
for i in 0..<m { | |
j = mod(a: i+1, n: m) | |
k = mod(a: i+2, n: m) | |
p4 = interval(lambda: 1/2.0, a: curve.vertex[k], b: curve.vertex[j]) | |
denom = ddenom(p0: curve.vertex[i], p2: curve.vertex[k]) | |
if denom != 0.0 { | |
dd = dpara(p0: curve.vertex[i], p1: curve.vertex[j], p2: curve.vertex[k]) / denom | |
dd = abs(dd) | |
alpha = dd>1 ? (1 - 1.0/dd) : 0 | |
alpha /= 0.75 | |
} else { | |
alpha = 4/3.0 | |
} | |
curve.alpha0[j] = alpha | |
if alpha >= info.alphamax { | |
curve.tag[j] = "CORNER" | |
curve.c[3 * j + 1] = curve.vertex[j] | |
curve.c[3 * j + 2] = p4 | |
} else { | |
if alpha < 0.55 { | |
alpha = 0.55 | |
} else if alpha > 1 { | |
alpha = 1 | |
} | |
p2 = interval(lambda: 0.5+0.5*alpha, a: curve.vertex[i], b: curve.vertex[j]) | |
p3 = interval(lambda: 0.5+0.5*alpha, a: curve.vertex[k], b: curve.vertex[j]) | |
curve.tag[j] = "CURVE" | |
curve.c[3 * j + 0] = p2 | |
curve.c[3 * j + 1] = p3 | |
curve.c[3 * j + 2] = p4 | |
} | |
curve.alpha[j] = alpha | |
curve.beta[j] = 0.5 | |
} | |
curve.alphacurve = 1 | |
} | |
func optiCurve(path: Path) { | |
class Opti { | |
var pen: Double | |
var c: BoxedArray<PointF> | |
var t: Double | |
var s: Double | |
var alpha: Double | |
init() { | |
self.pen = 0 | |
self.c = BoxedArray(repeating: PointF(x: 0, y: 0), count: 2) | |
self.t = 0 | |
self.s = 0 | |
self.alpha = 0 | |
} | |
} | |
func opti_penalty(path: Path, i: Int, j: Int, res: Opti, | |
opttolerance: Double, convc: Array<Int>, areac: Array<Double>) -> Int { | |
var m = path.curve.n, curve = path.curve, vertex = curve.vertex, | |
k: Int, k1: Int, k2: Int, conv: Int, i1: Int, | |
area: Double, alpha: Double, d: Double, d1: Double, d2: Double, | |
p0: PointF, p1: PointF, p2: PointF, p3: PointF, pt: PointF, | |
A: Double, R: Double, A1: Double, A2: Double, A3: Double, A4: Double, | |
s: Double, t: Double | |
if i == j { | |
return 1 | |
} | |
k = i | |
i1 = mod(a: i+1, n: m) | |
k1 = mod(a: k+1, n: m) | |
conv = convc[k1] | |
if (conv == 0) { | |
return 1 | |
} | |
d = ddist(p: vertex[i], q: vertex[i1]) | |
k = k1 | |
while k != j { | |
k1 = mod(a: k+1, n: m) | |
k2 = mod(a: k+2, n: m) | |
if (convc[k1] != conv) { | |
return 1 | |
} | |
if (sign(i: Int(cprod(p0: vertex[i], p1: vertex[i1], p2: vertex[k1], p3: vertex[k2]))) != | |
conv) { | |
return 1 | |
} | |
if (iprod1(p0: vertex[i], p1: vertex[i1], p2: vertex[k1], p3: vertex[k2]) < | |
d * ddist(p: vertex[k1], q: vertex[k2]) * -0.999847695156) { | |
return 1 | |
} | |
k = k1 | |
} | |
p0 = curve.c[mod(a: i,n: m) * 3 + 2].copy() | |
p1 = vertex[mod(a: i+1,n: m)].copy() | |
p2 = vertex[mod(a: j,n: m)].copy() | |
p3 = curve.c[mod(a: j,n: m) * 3 + 2].copy() | |
area = areac[j] - areac[i] | |
area -= dpara(p0: vertex[0], p1: curve.c[i * 3 + 2], p2: curve.c[j * 3 + 2])/2 | |
if (i>=j) { | |
area += areac[m] | |
} | |
A1 = dpara(p0: p0, p1: p1, p2: p2) | |
A2 = dpara(p0: p0, p1: p1, p2: p3) | |
A3 = dpara(p0: p0, p1: p2, p2: p3) | |
A4 = A1+A3-A2 | |
if (A2 == A1) { | |
return 1 | |
} | |
t = A3/(A3-A4) | |
s = A2/(A2-A1) | |
A = A2 * t / 2.0 | |
if (A == 0.0) { | |
return 1 | |
} | |
R = area / A | |
alpha = 2 - sqrt(4 - R / 0.3) | |
res.c[0] = interval(lambda: t * alpha, a: p0, b: p1) | |
res.c[1] = interval(lambda: s * alpha, a: p3, b: p2) | |
res.alpha = alpha | |
res.t = t | |
res.s = s | |
p1 = res.c[0].copy() | |
p2 = res.c[1].copy() | |
res.pen = 0 | |
k = mod(a: i+1,n: m) | |
while k != j { | |
k1 = mod(a: k+1,n: m) | |
t = tangent(p0: p0, p1: p1, p2: p2, p3: p3, q0: vertex[k], q1: vertex[k1]) | |
if (t < -0.5) { | |
return 1 | |
} | |
pt = bezier(t: t, p0: p0, p1: p1, p2: p2, p3: p3) | |
d = ddist(p: vertex[k], q: vertex[k1]) | |
if (d == 0.0) { | |
return 1 | |
} | |
d1 = dpara(p0: vertex[k], p1: vertex[k1], p2: pt) / d | |
if (abs(d1) > opttolerance) { | |
return 1 | |
} | |
if (iprod(p0: vertex[k], p1: vertex[k1], p2: pt) < 0 || | |
iprod(p0: vertex[k1], p1: vertex[k], p2: pt) < 0) { | |
return 1 | |
} | |
res.pen += d1 * d1 | |
k=k1 | |
} | |
k = i | |
while k == j { | |
k1 = mod(a: k+1,n: m) | |
t = tangent(p0: p0, p1: p1, p2: p2, p3: p3, q0: curve.c[k * 3 + 2], q1: curve.c[k1 * 3 + 2]) | |
if (t < -0.5) { | |
return 1 | |
} | |
pt = bezier(t: t, p0: p0, p1: p1, p2: p2, p3: p3) | |
d = ddist(p: curve.c[k * 3 + 2], q: curve.c[k1 * 3 + 2]) | |
if (d == 0.0) { | |
return 1 | |
} | |
d1 = dpara(p0: curve.c[k * 3 + 2], p1: curve.c[k1 * 3 + 2], p2: pt) / d | |
d2 = dpara(p0: curve.c[k * 3 + 2], p1: curve.c[k1 * 3 + 2], p2: vertex[k1]) / d | |
d2 *= 0.75 * curve.alpha[k1] | |
if (d2 < 0) { | |
d1 = -d1 | |
d2 = -d2 | |
} | |
if (d1 < d2 - opttolerance) { | |
return 1 | |
} | |
if (d1 < d2) { | |
res.pen += (d1 - d2) * (d1 - d2) | |
} | |
k=k1 | |
} | |
return 0 | |
} | |
var curve = path.curve, m = curve.n, vert = curve.vertex, | |
pt = Array<Int>(repeating: 0, count: m + 1), | |
pen = Array<Double>(repeating: 0, count: m + 1), | |
len = Array<Int>(repeating: 0, count: m + 1), | |
opt = Array<Opti>(repeating: Opti(), count: m + 1), | |
om: Int, i: Int,j: Int,r: Int, | |
o = Opti(), p0: PointF, | |
i1: Int, area: Double, alpha: Double, ocurve: Curve, | |
s: Array<Double>, t: Array<Double> | |
var convc = Array<Int>(repeating: 0, count: m), | |
areac = Array<Double>(repeating: 0, count: m+1) | |
for i in 0..<m { | |
if (curve.tag[i] == "CURVE") { | |
let val = dpara(p0: vert[mod(a: i-1,n: m)], p1: vert[i], p2: vert[mod(a: i+1,n: m)]) | |
convc[i] = sign(i: Int(val)) | |
} else { | |
convc[i] = 0 | |
} | |
} | |
area = 0.0 | |
areac[0] = 0.0 | |
p0 = curve.vertex[0] | |
for i in 0..<m { | |
i1 = mod(a: i+1, n: m) | |
if (curve.tag[i1] == "CURVE") { | |
alpha = curve.alpha[i1] | |
area += 0.3 * alpha * (4-alpha) * | |
dpara(p0: curve.c[i * 3 + 2], p1: vert[i1], p2: curve.c[i1 * 3 + 2])/2 | |
area += dpara(p0: p0, p1: curve.c[i * 3 + 2], p2: curve.c[i1 * 3 + 2])/2 | |
} | |
areac[i+1] = area | |
} | |
pt[0] = -1 | |
pen[0] = 0 | |
len[0] = 0 | |
for j in 1...m { | |
pt[j] = j-1 | |
pen[j] = pen[j-1] | |
len[j] = len[j-1]+1 | |
i = j - 2 | |
while i>=0 { | |
r = opti_penalty(path: path, i: i, j: mod(a: j,n: m), res: o, opttolerance: info.opttolerance, convc: convc, | |
areac: areac) | |
if r == 1 { | |
break | |
} | |
if (len[j] > len[i]+1 || | |
(len[j] == len[i]+1 && pen[j] > pen[i] + o.pen)) { | |
pt[j] = i | |
pen[j] = pen[i] + o.pen | |
len[j] = len[i] + 1 | |
opt[j] = o | |
o = Opti() | |
} | |
i -= 1 | |
} | |
} | |
om = len[m] | |
ocurve = Curve(n: om) | |
s = Array<Double>(repeating: 0, count: om) | |
t = Array<Double>(repeating: 0, count: om) | |
j = m | |
for i in (0...om-1).reversed() { | |
if (pt[j]==j-1) { | |
ocurve.tag[i] = curve.tag[mod(a: j,n: m)] | |
ocurve.c[i * 3 + 0] = curve.c[mod(a: j,n: m) * 3 + 0] | |
ocurve.c[i * 3 + 1] = curve.c[mod(a: j,n: m) * 3 + 1] | |
ocurve.c[i * 3 + 2] = curve.c[mod(a: j,n: m) * 3 + 2] | |
ocurve.vertex[i] = curve.vertex[mod(a: j,n: m)] | |
ocurve.alpha[i] = curve.alpha[mod(a: j,n: m)] | |
ocurve.alpha0[i] = curve.alpha0[mod(a: j,n: m)] | |
ocurve.beta[i] = curve.beta[mod(a: j,n: m)] | |
s[i] = 1.0 | |
t[i] = 1.0 | |
} else { | |
ocurve.tag[i] = "CURVE" | |
ocurve.c[i * 3 + 0] = opt[j].c[0] | |
ocurve.c[i * 3 + 1] = opt[j].c[1] | |
ocurve.c[i * 3 + 2] = curve.c[mod(a: j,n: m) * 3 + 2] | |
ocurve.vertex[i] = interval(lambda: opt[j].s, a: curve.c[mod(a: j,n: m) * 3 + 2], | |
b: vert[mod(a: j,n: m)]) | |
ocurve.alpha[i] = opt[j].alpha | |
ocurve.alpha0[i] = opt[j].alpha | |
s[i] = opt[j].s | |
t[i] = opt[j].t | |
} | |
j = pt[j] | |
} | |
for i in 0..<om { | |
i1 = mod(a: i+1,n: om) | |
ocurve.beta[i] = s[i] / (s[i] + t[i1]) | |
} | |
ocurve.alphacurve = 1 | |
path.curve = ocurve | |
} | |
for i in 0..<pathlist.count { | |
let path = pathlist[i] | |
calcSums(path: path) | |
calcLon(path: path) | |
bestPolygon(path: path) | |
adjustVertices(path: path) | |
if (path.sign == "-") { | |
reverse(path: path) | |
} | |
smooth(path: path) | |
if (info.optcurve) { | |
optiCurve(path: path) | |
} | |
} | |
} | |
func getBezierPath(scale size: Double = 1.0) -> NSBezierPath { | |
func path(curve: Curve, bezierPath: NSBezierPath) { | |
func bezier(i: Int) { | |
bezierPath.addCurve(to: CGPoint(x: (curve.c[i * 3 + 2].x * size), y: (curve.c[i * 3 + 2].y * size)), | |
controlPoint1: CGPoint(x: (curve.c[i * 3 + 0].x * size), y: (curve.c[i * 3 + 0].y * size)), | |
controlPoint2: CGPoint(x: (curve.c[i * 3 + 1].x * size), y: (curve.c[i * 3 + 1].y * size))) | |
} | |
func segment(i: Int) { | |
bezierPath.addLine(to: CGPoint(x: (curve.c[i * 3 + 1].x * size), y: (curve.c[i * 3 + 1].y * size))) | |
bezierPath.addLine(to: CGPoint(x: (curve.c[i * 3 + 2].x * size), y: (curve.c[i * 3 + 2].y * size))) | |
} | |
let n = curve.n | |
let x = (curve.c[(n - 1) * 3 + 2].x * size) | |
let y = (curve.c[(n - 1) * 3 + 2].y * size) | |
bezierPath.move(to: CGPoint(x: x, y: y)) | |
for i in 0..<n { | |
if (curve.tag[i] == "CURVE") { | |
bezier(i: i) | |
} else if (curve.tag[i] == "CORNER") { | |
segment(i: i) | |
} | |
} | |
} | |
let len = pathlist.count | |
var c: Curve | |
let bezierPath = NSBezierPath() | |
for i in 0 ..< len { | |
c = pathlist[i].curve | |
path(curve: c, bezierPath: bezierPath) | |
} | |
bezierPath.usesEvenOddFillRule = true | |
return bezierPath | |
} | |
func getSVG(scale size: Double = 1.0, opt_type: String? = nil) -> String { | |
func path(curve: Curve) -> String { | |
func bezier(i: Int) -> String { | |
var b = "C " + String(format: "%f", (curve.c[i * 3 + 0].x * size).toFixed(3)) + " " + | |
String(format: "%f",(curve.c[i * 3 + 0].y * size).toFixed(3)) + "," | |
b += String(format: "%f", (curve.c[i * 3 + 1].x * size).toFixed(3)) + " " + | |
String(format: "%f", (curve.c[i * 3 + 1].y * size).toFixed(3)) + "," | |
b += String(format: "%f", (curve.c[i * 3 + 2].x * size).toFixed(3)) + " " + | |
String(format: "%f", (curve.c[i * 3 + 2].y * size).toFixed(3)) + " " | |
return b | |
} | |
func segment(i: Int) -> String { | |
var s = "L " + String(format: "%f", (curve.c[i * 3 + 1].x * size).toFixed(3)) + " " + | |
String(format: "%f", (curve.c[i * 3 + 1].y * size).toFixed(3)) + " " | |
s += String(format: "%f", (curve.c[i * 3 + 2].x * size).toFixed(3)) + " " + | |
String(format: "%f", (curve.c[i * 3 + 2].y * size).toFixed(3)) + " " | |
return s | |
} | |
let n = curve.n | |
var p = "M" + String(format: "%f", (curve.c[(n - 1) * 3 + 2].x * size).toFixed(3)) + | |
" " + String(format: "%f", (curve.c[(n - 1) * 3 + 2].y * size).toFixed(3)) + " " | |
for i in 0..<n { | |
if (curve.tag[i] == "CURVE") { | |
p += bezier(i: i) | |
} else if (curve.tag[i] == "CORNER") { | |
p += segment(i: i) | |
} | |
} | |
return p | |
} | |
var w = Double(bm.w) * size, h = Double(bm.h) * size, | |
len = pathlist.count, c: Curve, strokec: String, fillc: String, fillrule: String | |
var svg = "<svg id=\"svg\" version=\"1.1\" width=\"\(w)\" height=\"\(h)\" xmlns=\"http://www.w3.org/2000/svg\">" | |
svg += "<path d=\"" | |
for i in 0 ..< len { | |
c = pathlist[i].curve | |
svg += path(curve: c) | |
} | |
if (opt_type == "curve") { | |
strokec = "black" | |
fillc = "none" | |
fillrule = "" | |
} else { | |
strokec = "none" | |
fillc = "black" | |
fillrule = " fill-rule=\"evenodd\"" | |
} | |
svg += "\" stroke=\"\(strokec)\" fill=\"\(fillc)\" \(fillrule)/></svg>" | |
return svg | |
} | |
} | |
extension Double { | |
func toFixed(_ places: Int) -> Double { | |
let divisor = pow(10.0, Double(places)) | |
return (self * divisor).rounded() / divisor | |
} | |
} | |
class BoxedArray<T> : MutableCollection { | |
var array: [T] | |
init() { | |
self.array = [T]() | |
} | |
init(repeating: T, count: Int) { | |
self.array = [T](repeating: repeating, count: count) | |
} | |
func index(after i: (Int)) -> (Int) { | |
return array.index(after: i) | |
} | |
var endIndex: (Int) { | |
return array.endIndex | |
} | |
var startIndex: (Int) { | |
return array.startIndex | |
} | |
func append(_ item: T) { | |
array.append(item) | |
} | |
subscript (index: Int) -> T { | |
get { return array[index] } | |
set(newValue) { array[index] = newValue } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment