import Foundation import UIKit import QuartzCore import simd import MediaEditor extension UIBezierPath { convenience init(roundRect rect: CGRect, topLeftRadius: CGFloat = 0.0, topRightRadius: CGFloat = 0.0, bottomLeftRadius: CGFloat = 0.0, bottomRightRadius: CGFloat = 0.0) { self.init() let path = CGMutablePath() let topLeft = rect.origin let topRight = CGPoint(x: rect.maxX, y: rect.minY) let bottomRight = CGPoint(x: rect.maxX, y: rect.maxY) let bottomLeft = CGPoint(x: rect.minX, y: rect.maxY) if topLeftRadius != .zero { path.move(to: CGPoint(x: topLeft.x+topLeftRadius, y: topLeft.y)) } else { path.move(to: CGPoint(x: topLeft.x, y: topLeft.y)) } if topRightRadius != .zero { path.addLine(to: CGPoint(x: topRight.x-topRightRadius, y: topRight.y)) path.addCurve(to: CGPoint(x: topRight.x, y: topRight.y+topRightRadius), control1: CGPoint(x: topRight.x, y: topRight.y), control2:CGPoint(x: topRight.x, y: topRight.y + topRightRadius)) } else { path.addLine(to: CGPoint(x: topRight.x, y: topRight.y)) } if bottomRightRadius != .zero { path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y-bottomRightRadius)) path.addCurve(to: CGPoint(x: bottomRight.x-bottomRightRadius, y: bottomRight.y), control1: CGPoint(x: bottomRight.x, y: bottomRight.y), control2: CGPoint(x: bottomRight.x-bottomRightRadius, y: bottomRight.y)) } else { path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y)) } if bottomLeftRadius != .zero { path.addLine(to: CGPoint(x: bottomLeft.x+bottomLeftRadius, y: bottomLeft.y)) path.addCurve(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius), control1: CGPoint(x: bottomLeft.x, y: bottomLeft.y), control2: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius)) } else { path.addLine(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y)) } if topLeftRadius != .zero { path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y+topLeftRadius)) path.addCurve(to: CGPoint(x: topLeft.x+topLeftRadius, y: topLeft.y) , control1: CGPoint(x: topLeft.x, y: topLeft.y) , control2: CGPoint(x: topLeft.x+topLeftRadius, y: topLeft.y)) } else { path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y)) } path.closeSubpath() self.cgPath = path } } extension CGPoint { func isEqual(to point: CGPoint, epsilon: CGFloat) -> Bool { if x - epsilon <= point.x && point.x <= x + epsilon && y - epsilon <= point.y && point.y <= y + epsilon { return true } return false } static public func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint { return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) } static public func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint { return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y) } static public func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint { return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs) } static public func / (lhs: CGPoint, rhs: CGFloat) -> CGPoint { return CGPoint(x: lhs.x / rhs, y: lhs.y / rhs) } var length: CGFloat { return sqrt(self.x * self.x + self.y * self.y) } static func middle(p1: CGPoint, p2: CGPoint) -> CGPoint { return CGPoint(x: (p1.x + p2.x) * 0.5, y: (p1.y + p2.y) * 0.5) } func distance(to point: CGPoint) -> CGFloat { return sqrt(pow((point.x - self.x), 2) + pow((point.y - self.y), 2)) } func distanceSquared(to point: CGPoint) -> CGFloat { return pow((point.x - self.x), 2) + pow((point.y - self.y), 2) } func angle(to point: CGPoint) -> CGFloat { return atan2((point.y - self.y), (point.x - self.x)) } func pointAt(distance: CGFloat, angle: CGFloat) -> CGPoint { return CGPoint(x: distance * cos(angle) + self.x, y: distance * sin(angle) + self.y) } func point(to point: CGPoint, t: CGFloat) -> CGPoint { return CGPoint(x: self.x + t * (point.x - self.x), y: self.y + t * (point.y - self.y)) } func perpendicularPointOnLine(start: CGPoint, end: CGPoint) -> CGPoint { let l2 = start.distanceSquared(to: end) if l2.isZero { return start } let t = ((self.x - start.x) * (end.x - start.x) + (self.y - start.y) * (end.y - start.y)) / l2 return CGPoint(x: start.x + t * (end.x - start.x), y: start.y + t * (end.y - start.y)) } func linearBezierPoint(to: CGPoint, t: CGFloat) -> CGPoint { let dx = to.x - x; let dy = to.y - y; let px = x + (t * dx); let py = y + (t * dy); return CGPoint(x: px, y: py) } fileprivate func _cubicBezier(_ t: CGFloat, _ start: CGFloat, _ c1: CGFloat, _ c2: CGFloat, _ end: CGFloat) -> CGFloat { let _t = 1 - t; let _t2 = _t * _t; let _t3 = _t * _t * _t ; let t2 = t * t; let t3 = t * t * t; return _t3 * start + 3.0 * _t2 * t * c1 + 3.0 * _t * t2 * c2 + t3 * end; } func cubicBezierPoint(to: CGPoint, controlPoint1 c1: CGPoint, controlPoint2 c2: CGPoint, t: CGFloat) -> CGPoint { let x = _cubicBezier(t, self.x, c1.x, c2.x, to.x); let y = _cubicBezier(t, self.y, c1.y, c2.y, to.y); return CGPoint(x: x, y: y); } fileprivate func _quadBezier(_ t: CGFloat, _ start: CGFloat, _ c1: CGFloat, _ end: CGFloat) -> CGFloat { let _t = 1 - t; let _t2 = _t * _t; let t2 = t * t; return _t2 * start + 2 * _t * t * c1 + t2 * end; } func quadBezierPoint(to: CGPoint, controlPoint: CGPoint, t: CGFloat) -> CGPoint { let x = _quadBezier(t, self.x, controlPoint.x, to.x); let y = _quadBezier(t, self.y, controlPoint.y, to.y); return CGPoint(x: x, y: y); } } extension CGPath { static func star(in rect: CGRect, extrusion: CGFloat, points: Int = 5) -> CGPath { func pointFrom(angle: CGFloat, radius: CGFloat, offset: CGPoint) -> CGPoint { return CGPoint(x: radius * cos(angle) + offset.x, y: radius * sin(angle) + offset.y) } let path = CGMutablePath() let center = rect.center.offsetBy(dx: 0.0, dy: rect.height * 0.05) var angle: CGFloat = -CGFloat(.pi / 2.0) let angleIncrement = CGFloat(.pi * 2.0 / Double(points)) let radius = rect.width / 2.0 var firstPoint = true for _ in 1 ... points { let point = center.pointAt(distance: radius, angle: angle) let nextPoint = center.pointAt(distance: radius, angle: angle + angleIncrement) let midPoint = center.pointAt(distance: extrusion, angle: angle + angleIncrement * 0.5) if firstPoint { firstPoint = false path.move(to: point) } path.addLine(to: midPoint) path.addLine(to: nextPoint) angle += angleIncrement } path.closeSubpath() return path } static func arrow(from point: CGPoint, controlPoint: CGPoint, width: CGFloat, height: CGFloat, isOpen: Bool) -> CGPath { let angle = atan2(point.y - controlPoint.y, point.x - controlPoint.x) let angleAdjustment = atan2(width, -height) let distance = hypot(width, height) let path = CGMutablePath() path.move(to: point) path.addLine(to: point.pointAt(distance: distance, angle: angle - angleAdjustment)) if isOpen { path.addLine(to: point) } path.addLine(to: point.pointAt(distance: distance, angle: angle + angleAdjustment)) if isOpen { path.addLine(to: point) } else { path.closeSubpath() } return path } static func curve(start: CGPoint, end: CGPoint, mid: CGPoint, lineWidth: CGFloat?, arrowSize: CGSize?, twoSided: Bool = false) -> CGPath { let linePath = CGMutablePath() let controlPoints = configureControlPoints(data: [start, mid, end]) var lineStart = start if let arrowSize = arrowSize, twoSided { lineStart = start.pointAt(distance: -arrowSize.height * 0.5, angle: controlPoints[0].ctrl1.angle(to: start)) } linePath.move(to: lineStart) linePath.addCurve(to: mid, control1: controlPoints[0].ctrl1, control2: controlPoints[0].ctrl2) var lineEnd = end if let arrowSize = arrowSize { lineEnd = end.pointAt(distance: -arrowSize.height * 0.5, angle: controlPoints[1].ctrl1.angle(to: end)) } linePath.addCurve(to: lineEnd, control1: controlPoints[1].ctrl1, control2: controlPoints[1].ctrl2) let path: CGMutablePath if let lineWidth = lineWidth, let mutablePath = linePath.copy(strokingWithWidth: lineWidth, lineCap: .square, lineJoin: .round, miterLimit: 0.0).mutableCopy() { path = mutablePath } else { path = linePath } if let arrowSize = arrowSize { let arrowPath = arrow(from: end, controlPoint: controlPoints[1].ctrl1, width: arrowSize.width, height: arrowSize.height, isOpen: false) path.addPath(arrowPath) if twoSided { let secondArrowPath = arrow(from: start, controlPoint: controlPoints[0].ctrl1, width: arrowSize.width, height: arrowSize.height, isOpen: false) path.addPath(secondArrowPath) } } return path } static func bubble(in rect: CGRect, cornerRadius: CGFloat, smallCornerRadius: CGFloat, tailPosition: CGPoint, tailWidth: CGFloat) -> CGPath { let r1 = min(cornerRadius, min(rect.width, rect.height) / 3.0) let r2 = min(smallCornerRadius, min(rect.width, rect.height) / 10.0) let ax = tailPosition.x * rect.width let ay = tailPosition.y let width = min(max(tailWidth, ay / 2.0), rect.width / 4.0) let angle = atan2(ay, width) let h = r2 / tan(angle / 2.0) let r1a = min(r1, min(rect.maxX - ax, ax - rect.minX) * 0.5) let r2a = min(r2, min(rect.maxX - ax, ax - rect.minX) * 0.2) let path = CGMutablePath() path.addArc(center: CGPoint(x: rect.minX + r1, y: rect.minY + r1), radius: r1, startAngle: .pi, endAngle: .pi * 3.0 / 2.0, clockwise: false) path.addArc(center: CGPoint(x: rect.maxX - r1, y: rect.minY + r1), radius: r1, startAngle: -.pi / 2.0, endAngle: 0.0, clockwise: false) if ax > rect.width / 2.0 { if ax < rect.width - 1 { path.addArc(center: CGPoint(x: rect.maxX - r1a, y: rect.maxY - r1a), radius: r1a, startAngle: 0.0, endAngle: .pi / 2.0, clockwise: false) path.addArc(center: CGPoint(x: rect.minX + ax + r2a, y: rect.maxY + r2a), radius: r2a, startAngle: .pi * 3.0 / 2.0, endAngle: .pi, clockwise: true) } path.addLine(to: CGPoint(x: rect.minX + ax, y: rect.maxY + ay)) path.addArc(center: CGPoint(x: rect.minX + ax - width - r2, y: rect.maxY + h), radius: h, startAngle: -(CGFloat.pi / 2 - angle), endAngle: CGFloat.pi * 3 / 2, clockwise: true) path.addArc(center: CGPoint(x: rect.minX + r1, y: rect.maxY - r1), radius: r1, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi, clockwise: false) } else { path.addArc(center: CGPoint(x: rect.maxX - r1, y: rect.maxY - r1), radius: r1, startAngle: 0, endAngle: CGFloat.pi / 2, clockwise: false) path.addArc(center: CGPoint(x: rect.minX + ax + width + r2, y: rect.maxY + h), radius: h, startAngle: CGFloat.pi * 3 / 2, endAngle: CGFloat.pi * 3 / 2 - angle, clockwise: true) path.addLine(to: CGPoint(x: rect.minX + ax, y: rect.maxY + ay)) if ax > 1 { path.addArc(center: CGPoint(x: rect.minX + ax - r2a, y: rect.maxY + r2a), radius: r2a, startAngle: 0, endAngle: CGFloat.pi * 3 / 2, clockwise: true) path.addArc(center: CGPoint(x: rect.minX + r1a, y: rect.maxY - r1a), radius: r1a, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi, clockwise: false) } } path.closeSubpath() return path } } private func configureControlPoints(data: [CGPoint]) -> [(ctrl1: CGPoint, ctrl2: CGPoint)] { let segments = data.count - 1 if segments == 1 { let p0 = data[0] let p3 = data[1] return [(p0, p3)] } else if segments > 1 { var ad: [CGFloat] = [] var d: [CGFloat] = [] var bd: [CGFloat] = [] var rhsArray: [CGPoint] = [] for i in 0 ..< segments { var rhsXValue: CGFloat = 0.0 var rhsYValue: CGFloat = 0.0 let p0 = data[i] let p3 = data[i + 1] if i == 0 { bd.append(0.0) d.append(2.0) ad.append(1.0) rhsXValue = p0.x + 2.0 * p3.x rhsYValue = p0.y + 2.0 * p3.y } else if i == segments - 1 { bd.append(2.0) d.append(7.0) ad.append(0.0) rhsXValue = 8.0 * p0.x + p3.x rhsYValue = 8.0 * p0.y + p3.y } else { bd.append(1.0) d.append(4.0) ad.append(1.0) rhsXValue = 4.0 * p0.x + 2.0 * p3.x rhsYValue = 4.0 * p0.y + 2.0 * p3.y } rhsArray.append(CGPoint(x: rhsXValue, y: rhsYValue)) } var firstControlPoints: [CGPoint?] = [] var secondControlPoints: [CGPoint?] = [] var controlPoints : [(CGPoint, CGPoint)] = [] var solutionSet1 = [CGPoint?]() solutionSet1 = Array(repeating: nil, count: segments) ad[0] = ad[0] / d[0] rhsArray[0].x = rhsArray[0].x / d[0] rhsArray[0].y = rhsArray[0].y / d[0] if segments > 2 { for i in 1...segments - 2 { let rhsValueX = rhsArray[i].x let prevRhsValueX = rhsArray[i - 1].x let rhsValueY = rhsArray[i].y let prevRhsValueY = rhsArray[i - 1].y ad[i] = ad[i] / (d[i] - bd[i] * ad[i - 1]); let exp1x = (rhsValueX - (bd[i] * prevRhsValueX)) let exp1y = (rhsValueY - (bd[i] * prevRhsValueY)) let exp2 = (d[i] - bd[i] * ad[i - 1]) rhsArray[i].x = exp1x / exp2 rhsArray[i].y = exp1y / exp2 } } let lastElementIndex = segments - 1 let exp1 = (rhsArray[lastElementIndex].x - bd[lastElementIndex] * rhsArray[lastElementIndex - 1].x) let exp1y = (rhsArray[lastElementIndex].y - bd[lastElementIndex] * rhsArray[lastElementIndex - 1].y) let exp2 = (d[lastElementIndex] - bd[lastElementIndex] * ad[lastElementIndex - 1]) rhsArray[lastElementIndex].x = exp1 / exp2 rhsArray[lastElementIndex].y = exp1y / exp2 solutionSet1[lastElementIndex] = rhsArray[lastElementIndex] for i in (0.. Matrix { m[12] = x m[13] = y m[14] = z return self } @discardableResult func scaling(x: Float, y: Float, z: Float) -> Matrix { m[0] = x m[5] = y m[10] = z return self } static var identity = Matrix() } struct Vertex { var position: vector_float4 var texCoord: vector_float2 init(position: CGPoint, texCoord: CGPoint) { self.position = position.toFloat4() self.texCoord = texCoord.toFloat2() } } struct Point { var position: vector_float4 var color: vector_float4 var angle: Float var size: Float init(x: CGFloat, y: CGFloat, color: DrawingColor, size: CGFloat, angle: CGFloat = 0) { self.position = vector_float4(Float(x), Float(y), 0, 1) self.size = Float(size) self.color = color.toFloat4() self.angle = Float(angle) } } extension CGPoint { func toFloat4(z: CGFloat = 0, w: CGFloat = 1) -> vector_float4 { return [Float(x), Float(y), Float(z) ,Float(w)] } func toFloat2() -> vector_float2 { return [Float(x), Float(y)] } func offsetBy(_ offset: CGPoint) -> CGPoint { return self.offsetBy(dx: offset.x, dy: offset.y) } } func normalizeDrawingRect(_ rect: CGRect, drawingSize: CGSize) -> CGRect { var rect = rect if rect.origin.x < 0.0 { rect.size.width += rect.origin.x rect.origin.x = 0.0 } if rect.origin.y < 0.0 { rect.size.height += rect.origin.y rect.origin.y = 0.0 } if rect.maxX > drawingSize.width { rect.size.width -= (rect.maxX - drawingSize.width) } if rect.maxY > drawingSize.height { rect.size.height -= (rect.maxY - drawingSize.height) } return rect } extension CATransform3D { func decompose() -> (translation: SIMD3, rotation: SIMD3, scale: SIMD3) { let m0 = SIMD3(Float(self.m11), Float(self.m12), Float(self.m13)) let m1 = SIMD3(Float(self.m21), Float(self.m22), Float(self.m23)) let m2 = SIMD3(Float(self.m31), Float(self.m32), Float(self.m33)) let m3 = SIMD3(Float(self.m41), Float(self.m42), Float(self.m43)) let t = m3 let sx = length(m0) let sy = length(m1) let sz = length(m2) let s = SIMD3(sx, sy, sz) let rx = m0 / sx let ry = m1 / sy let rz = m2 / sz let pitch = atan2(ry.z, rz.z) let yaw = atan2(-rx.z, hypot(ry.z, rz.z)) let roll = atan2(rx.y, rx.x) let r = SIMD3(pitch, yaw, roll) return (t, r, s) } }