mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Drawing improvements
This commit is contained in:
parent
108073a6b8
commit
1a55c2f626
@ -201,6 +201,9 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
||||
|
||||
var entitiesData: Data? {
|
||||
let entities = self.entities
|
||||
guard !entities.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
for entity in entities {
|
||||
entity.prepareForRender()
|
||||
}
|
||||
|
@ -17,9 +17,7 @@ final class DrawingMetalView: MTKView {
|
||||
private var render_target_vertex: MTLBuffer!
|
||||
private var render_target_uniform: MTLBuffer!
|
||||
|
||||
private var penBrush: Brush?
|
||||
private var markerBrush: Brush?
|
||||
private var pencilBrush: Brush?
|
||||
|
||||
init?(size: CGSize) {
|
||||
var size = size
|
||||
@ -75,12 +73,6 @@ final class DrawingMetalView: MTKView {
|
||||
}
|
||||
|
||||
func drawInContext(_ cgContext: CGContext) {
|
||||
// guard let texture = self.drawable?.texture, let ciImage = CIImage(mtlTexture: texture, options: [.colorSpace: CGColorSpaceCreateDeviceRGB()])?.oriented(forExifOrientation: 1) else {
|
||||
// return
|
||||
// }
|
||||
// let context = CIContext(cgContext: cgContext)
|
||||
// let rect = CGRect(origin: .zero, size: ciImage.extent.size)
|
||||
// context.draw(ciImage, in: rect, from: rect)
|
||||
guard let texture = self.drawable?.texture, let image = texture.createCGImage() else {
|
||||
return
|
||||
}
|
||||
@ -123,16 +115,10 @@ final class DrawingMetalView: MTKView {
|
||||
} catch {
|
||||
fatalError(error.localizedDescription)
|
||||
}
|
||||
|
||||
self.penBrush = Brush(texture: nil, target: self, rotation: .ahead)
|
||||
|
||||
|
||||
if let url = getAppBundle().url(forResource: "marker", withExtension: "png"), let data = try? Data(contentsOf: url) {
|
||||
self.markerBrush = Brush(texture: self.makeTexture(with: data), target: self, rotation: .fixed(-0.55))
|
||||
}
|
||||
|
||||
if let url = getAppBundle().url(forResource: "pencil", withExtension: "png"), let data = try? Data(contentsOf: url) {
|
||||
self.pencilBrush = Brush(texture: self.makeTexture(with: data), target: self, rotation: .random)
|
||||
}
|
||||
}
|
||||
|
||||
var clearOnce = false
|
||||
@ -184,30 +170,20 @@ final class DrawingMetalView: MTKView {
|
||||
}
|
||||
|
||||
enum BrushType {
|
||||
case pen
|
||||
case marker
|
||||
case pencil
|
||||
}
|
||||
|
||||
func updated(_ point: Polyline.Point, state: DrawingGesturePipeline.DrawingGestureState, brush: BrushType, color: DrawingColor, size: CGFloat) {
|
||||
switch brush {
|
||||
case .pen:
|
||||
self.penBrush?.updated(point, color: color, state: state, size: size)
|
||||
case .marker:
|
||||
self.markerBrush?.updated(point, color: color, state: state, size: size)
|
||||
case .pencil:
|
||||
self.pencilBrush?.updated(point, color: color, state: state, size: size)
|
||||
}
|
||||
}
|
||||
|
||||
func setup(_ points: [CGPoint], brush: BrushType, color: DrawingColor, size: CGFloat) {
|
||||
switch brush {
|
||||
case .pen:
|
||||
self.penBrush?.setup(points, color: color, size: size)
|
||||
case .marker:
|
||||
self.markerBrush?.setup(points, color: color, size: size)
|
||||
case .pencil:
|
||||
self.pencilBrush?.setup(points, color: color, size: size)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -353,7 +329,6 @@ private class Brush {
|
||||
target.prepareForDraw()
|
||||
|
||||
let commandEncoder = target.makeCommandEncoder()
|
||||
|
||||
commandEncoder?.setRenderPipelineState(self.pipelineState)
|
||||
|
||||
if let vertex_buffer = stroke.preparedBuffer(rotation: self.rotation) {
|
||||
|
@ -2511,6 +2511,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.statusBar.statusBarStyle = .Hide
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
}
|
||||
|
||||
public var drawingView: DrawingView {
|
||||
@ -2583,6 +2584,8 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
||||
image = finalImage
|
||||
}
|
||||
|
||||
let drawingData = self.drawingView.drawingData
|
||||
|
||||
let entitiesData = self.entitiesView.entitiesData
|
||||
|
||||
var stickers: [Any] = []
|
||||
@ -2600,7 +2603,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
||||
}
|
||||
}
|
||||
|
||||
return TGPaintingData(drawing: nil, entitiesData: entitiesData, image: image, stillImage: stillImage, hasAnimation: hasAnimatedEntities, stickers: stickers)
|
||||
return TGPaintingData(drawing: drawingData, entitiesData: entitiesData, image: image, stillImage: stillImage, hasAnimation: hasAnimatedEntities, stickers: stickers)
|
||||
}
|
||||
|
||||
public func resultImage() -> UIImage! {
|
||||
|
@ -6,53 +6,25 @@ protocol DrawingRenderLayer: CALayer {
|
||||
|
||||
}
|
||||
|
||||
final class MarkerTool: DrawingElement {
|
||||
let uuid = UUID()
|
||||
final class MarkerTool: DrawingElement, Codable {
|
||||
let uuid: UUID
|
||||
|
||||
let drawingSize: CGSize
|
||||
let color: DrawingColor
|
||||
let lineWidth: CGFloat
|
||||
let arrow: Bool
|
||||
|
||||
let renderLineWidth: CGFloat
|
||||
var renderPath = UIBezierPath()
|
||||
var renderAngle: CGFloat = 0.0
|
||||
|
||||
var translation = CGPoint()
|
||||
|
||||
var bounds: CGRect {
|
||||
return self.renderPath.bounds.offsetBy(dx: self.translation.x, dy: self.translation.y)
|
||||
}
|
||||
var points: [CGPoint] = []
|
||||
|
||||
var _points: [Polyline.Point] = []
|
||||
var points: [Polyline.Point] {
|
||||
return self._points.map { $0.offsetBy(self.translation) }
|
||||
}
|
||||
|
||||
weak var metalView: DrawingMetalView?
|
||||
|
||||
func containsPoint(_ point: CGPoint) -> Bool {
|
||||
return self.renderPath.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y)))
|
||||
}
|
||||
|
||||
func hasPointsInsidePath(_ path: UIBezierPath) -> Bool {
|
||||
let pathBoundingBox = path.bounds
|
||||
if self.bounds.intersects(pathBoundingBox) {
|
||||
for point in self._points {
|
||||
if path.contains(point.location.offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, arrow: Bool) {
|
||||
|
||||
required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat) {
|
||||
self.uuid = UUID()
|
||||
self.drawingSize = drawingSize
|
||||
self.color = color
|
||||
self.lineWidth = lineWidth
|
||||
self.arrow = arrow
|
||||
|
||||
|
||||
let minLineWidth = max(10.0, max(drawingSize.width, drawingSize.height) * 0.01)
|
||||
let maxLineWidth = max(20.0, max(drawingSize.width, drawingSize.height) * 0.09)
|
||||
let lineWidth = minLineWidth + (maxLineWidth - minLineWidth) * lineWidth
|
||||
@ -60,43 +32,64 @@ final class MarkerTool: DrawingElement {
|
||||
self.renderLineWidth = lineWidth
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case uuid
|
||||
case drawingSize
|
||||
case color
|
||||
case renderLineWidth
|
||||
case points
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.uuid = try container.decode(UUID.self, forKey: .uuid)
|
||||
self.drawingSize = try container.decode(CGSize.self, forKey: .drawingSize)
|
||||
self.color = try container.decode(DrawingColor.self, forKey: .color)
|
||||
self.renderLineWidth = try container.decode(CGFloat.self, forKey: .renderLineWidth)
|
||||
self.points = try container.decode([CGPoint].self, forKey: .points)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.uuid, forKey: .uuid)
|
||||
try container.encode(self.drawingSize, forKey: .drawingSize)
|
||||
try container.encode(self.color, forKey: .color)
|
||||
try container.encode(self.renderLineWidth, forKey: .renderLineWidth)
|
||||
try container.encode(self.points, forKey: .points)
|
||||
}
|
||||
|
||||
func setupRenderLayer() -> DrawingRenderLayer? {
|
||||
return nil
|
||||
}
|
||||
|
||||
private var hot = false
|
||||
private var didSetup = false
|
||||
func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) {
|
||||
guard case let .location(point) = path else {
|
||||
return
|
||||
}
|
||||
|
||||
if self._points.isEmpty {
|
||||
self.renderPath.move(to: point.location)
|
||||
} else {
|
||||
self.renderPath.addLine(to: point.location)
|
||||
}
|
||||
self._points.append(point)
|
||||
self.points.append(point.location)
|
||||
|
||||
self.hot = true
|
||||
self.didSetup = true
|
||||
self.metalView?.updated(point, state: state, brush: .marker, color: self.color, size: self.renderLineWidth)
|
||||
}
|
||||
|
||||
func draw(in context: CGContext, size: CGSize) {
|
||||
guard !self._points.isEmpty else {
|
||||
guard !self.points.isEmpty else {
|
||||
return
|
||||
}
|
||||
context.saveGState()
|
||||
|
||||
context.translateBy(x: self.translation.x, y: self.translation.y)
|
||||
|
||||
let hot = self.hot
|
||||
if hot {
|
||||
self.hot = false
|
||||
let didSetup = self.didSetup
|
||||
if didSetup {
|
||||
self.didSetup = false
|
||||
} else {
|
||||
self.metalView?.setup(self._points.map { $0.location }, brush: .marker, color: self.color, size: self.renderLineWidth)
|
||||
self.metalView?.setup(self.points, brush: .marker, color: self.color, size: self.renderLineWidth)
|
||||
}
|
||||
self.metalView?.drawInContext(context)
|
||||
if !hot {
|
||||
if !didSetup {
|
||||
self.metalView?.clear()
|
||||
}
|
||||
|
||||
@ -104,7 +97,7 @@ final class MarkerTool: DrawingElement {
|
||||
}
|
||||
}
|
||||
|
||||
final class NeonTool: DrawingElement {
|
||||
final class NeonTool: DrawingElement, Codable {
|
||||
class RenderLayer: SimpleLayer, DrawingRenderLayer {
|
||||
var lineWidth: CGFloat = 0.0
|
||||
|
||||
@ -132,7 +125,6 @@ final class NeonTool: DrawingElement {
|
||||
self.shadowLayer.shadowOpacity = 1.0
|
||||
self.shadowLayer.shadowOffset = .zero
|
||||
|
||||
|
||||
self.borderLayer.frame = bounds
|
||||
self.borderLayer.contentsScale = 1.0
|
||||
self.borderLayer.lineWidth = strokeWidth
|
||||
@ -141,7 +133,6 @@ final class NeonTool: DrawingElement {
|
||||
self.borderLayer.fillColor = UIColor.clear.cgColor
|
||||
self.borderLayer.strokeColor = UIColor.white.mixedWith(color.toUIColor(), alpha: 0.25).cgColor
|
||||
|
||||
|
||||
self.fillLayer.frame = bounds
|
||||
self.fillLayer.contentsScale = 1.0
|
||||
self.fillLayer.fillColor = UIColor.white.cgColor
|
||||
@ -158,16 +149,11 @@ final class NeonTool: DrawingElement {
|
||||
}
|
||||
}
|
||||
|
||||
let uuid = UUID()
|
||||
let uuid: UUID
|
||||
|
||||
let drawingSize: CGSize
|
||||
let color: DrawingColor
|
||||
let lineWidth: CGFloat
|
||||
let arrow: Bool
|
||||
|
||||
var path: BezierPath?
|
||||
var boundingBox: CGRect?
|
||||
|
||||
|
||||
var renderPath: CGPath?
|
||||
let renderStrokeWidth: CGFloat
|
||||
let renderShadowRadius: CGFloat
|
||||
@ -176,70 +162,11 @@ final class NeonTool: DrawingElement {
|
||||
var translation = CGPoint()
|
||||
|
||||
private var currentRenderLayer: DrawingRenderLayer?
|
||||
|
||||
var bounds: CGRect {
|
||||
return self.path?.path.bounds.offsetBy(dx: self.translation.x, dy: self.translation.y) ?? .zero
|
||||
}
|
||||
|
||||
var points: [Polyline.Point] {
|
||||
guard let linePath = self.path else {
|
||||
return []
|
||||
}
|
||||
var points: [Polyline.Point] = []
|
||||
for element in linePath.elements {
|
||||
if case .moveTo = element.type {
|
||||
points.append(element.startPoint.offsetBy(self.translation))
|
||||
} else {
|
||||
points.append(element.endPoint.offsetBy(self.translation))
|
||||
}
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
func containsPoint(_ point: CGPoint) -> Bool {
|
||||
return self.renderPath?.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y))) ?? false
|
||||
}
|
||||
|
||||
func hasPointsInsidePath(_ path: UIBezierPath) -> Bool {
|
||||
if let linePath = self.path {
|
||||
let pathBoundingBox = path.bounds
|
||||
if self.bounds.intersects(pathBoundingBox) {
|
||||
for element in linePath.elements {
|
||||
if case .moveTo = element.type {
|
||||
if path.contains(element.startPoint.location.offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if path.contains(element.startPoint.location.offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
if path.contains(element.endPoint.location.offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
if case .cubicCurve = element.type {
|
||||
if path.contains(element.controlPoints[0].offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
if path.contains(element.controlPoints[1].offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
} else if case .quadCurve = element.type {
|
||||
if path.contains(element.controlPoints[0].offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, arrow: Bool) {
|
||||
|
||||
required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat) {
|
||||
self.uuid = UUID()
|
||||
self.drawingSize = drawingSize
|
||||
self.color = color
|
||||
self.lineWidth = lineWidth
|
||||
self.arrow = arrow
|
||||
|
||||
let strokeWidth = min(drawingSize.width, drawingSize.height) * 0.008
|
||||
let shadowRadius = min(drawingSize.width, drawingSize.height) * 0.03
|
||||
@ -253,6 +180,37 @@ final class NeonTool: DrawingElement {
|
||||
self.renderLineWidth = lineWidth
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case uuid
|
||||
case drawingSize
|
||||
case color
|
||||
case renderStrokeWidth
|
||||
case renderShadowRadius
|
||||
case renderLineWidth
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.uuid = try container.decode(UUID.self, forKey: .uuid)
|
||||
self.drawingSize = try container.decode(CGSize.self, forKey: .drawingSize)
|
||||
self.color = try container.decode(DrawingColor.self, forKey: .color)
|
||||
self.renderStrokeWidth = try container.decode(CGFloat.self, forKey: .renderStrokeWidth)
|
||||
self.renderShadowRadius = try container.decode(CGFloat.self, forKey: .renderShadowRadius)
|
||||
self.renderLineWidth = try container.decode(CGFloat.self, forKey: .renderLineWidth)
|
||||
// self.points = try container.decode([CGPoint].self, forKey: .points)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.uuid, forKey: .uuid)
|
||||
try container.encode(self.drawingSize, forKey: .drawingSize)
|
||||
try container.encode(self.color, forKey: .color)
|
||||
try container.encode(self.renderStrokeWidth, forKey: .renderStrokeWidth)
|
||||
try container.encode(self.renderShadowRadius, forKey: .renderShadowRadius)
|
||||
try container.encode(self.renderLineWidth, forKey: .renderLineWidth)
|
||||
// try container.encode(self.points, forKey: .points)
|
||||
}
|
||||
|
||||
func setupRenderLayer() -> DrawingRenderLayer? {
|
||||
let layer = RenderLayer()
|
||||
layer.setup(size: self.drawingSize, color: self.color, lineWidth: self.renderLineWidth, strokeWidth: self.renderStrokeWidth, shadowRadius: self.renderShadowRadius)
|
||||
@ -265,8 +223,6 @@ final class NeonTool: DrawingElement {
|
||||
return
|
||||
}
|
||||
|
||||
self.path = bezierPath
|
||||
|
||||
let cgPath = bezierPath.path.cgPath.copy(strokingWithWidth: self.renderLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0)
|
||||
self.renderPath = cgPath
|
||||
|
||||
@ -310,28 +266,42 @@ final class NeonTool: DrawingElement {
|
||||
}
|
||||
}
|
||||
|
||||
final class FillTool: DrawingElement {
|
||||
let uuid = UUID()
|
||||
final class FillTool: DrawingElement, Codable {
|
||||
let uuid: UUID
|
||||
|
||||
let drawingSize: CGSize
|
||||
let color: DrawingColor
|
||||
let renderLineWidth: CGFloat = 0.0
|
||||
|
||||
var bounds: CGRect {
|
||||
return .zero
|
||||
}
|
||||
|
||||
var points: [Polyline.Point] {
|
||||
return []
|
||||
}
|
||||
|
||||
var translation = CGPoint()
|
||||
|
||||
required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, arrow: Bool) {
|
||||
required init(drawingSize: CGSize, color: DrawingColor) {
|
||||
self.uuid = UUID()
|
||||
self.drawingSize = drawingSize
|
||||
self.color = color
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case uuid
|
||||
case drawingSize
|
||||
case color
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.uuid = try container.decode(UUID.self, forKey: .uuid)
|
||||
self.drawingSize = try container.decode(CGSize.self, forKey: .drawingSize)
|
||||
self.color = try container.decode(DrawingColor.self, forKey: .color)
|
||||
// self.points = try container.decode([CGPoint].self, forKey: .points)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.uuid, forKey: .uuid)
|
||||
try container.encode(self.drawingSize, forKey: .drawingSize)
|
||||
try container.encode(self.color, forKey: .color)
|
||||
// try container.encode(self.points, forKey: .points)
|
||||
}
|
||||
|
||||
func setupRenderLayer() -> DrawingRenderLayer? {
|
||||
return nil
|
||||
}
|
||||
@ -360,14 +330,14 @@ final class FillTool: DrawingElement {
|
||||
}
|
||||
|
||||
|
||||
final class BlurTool: DrawingElement {
|
||||
final class BlurTool: DrawingElement, Codable {
|
||||
class RenderLayer: SimpleLayer, DrawingRenderLayer {
|
||||
var lineWidth: CGFloat = 0.0
|
||||
|
||||
let blurLayer = SimpleLayer()
|
||||
let fillLayer = SimpleShapeLayer()
|
||||
|
||||
func setup(size: CGSize, color: DrawingColor, lineWidth: CGFloat, image: UIImage?) {
|
||||
func setup(size: CGSize, lineWidth: CGFloat, image: UIImage?) {
|
||||
self.contentsScale = 1.0
|
||||
self.lineWidth = lineWidth
|
||||
|
||||
@ -399,15 +369,11 @@ final class BlurTool: DrawingElement {
|
||||
|
||||
var getFullImage: () -> UIImage? = { return nil }
|
||||
|
||||
let uuid = UUID()
|
||||
let uuid: UUID
|
||||
|
||||
let drawingSize: CGSize
|
||||
let color: DrawingColor
|
||||
let lineWidth: CGFloat
|
||||
let arrow: Bool
|
||||
|
||||
var path: BezierPath?
|
||||
var boundingBox: CGRect?
|
||||
|
||||
var renderPath: CGPath?
|
||||
let renderLineWidth: CGFloat
|
||||
@ -416,69 +382,9 @@ final class BlurTool: DrawingElement {
|
||||
|
||||
private var currentRenderLayer: DrawingRenderLayer?
|
||||
|
||||
var bounds: CGRect {
|
||||
return self.path?.path.bounds.offsetBy(dx: self.translation.x, dy: self.translation.y) ?? .zero
|
||||
}
|
||||
|
||||
var points: [Polyline.Point] {
|
||||
guard let linePath = self.path else {
|
||||
return []
|
||||
}
|
||||
var points: [Polyline.Point] = []
|
||||
for element in linePath.elements {
|
||||
if case .moveTo = element.type {
|
||||
points.append(element.startPoint.offsetBy(self.translation))
|
||||
} else {
|
||||
points.append(element.endPoint.offsetBy(self.translation))
|
||||
}
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
func containsPoint(_ point: CGPoint) -> Bool {
|
||||
return self.renderPath?.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y))) ?? false
|
||||
}
|
||||
|
||||
func hasPointsInsidePath(_ path: UIBezierPath) -> Bool {
|
||||
if let linePath = self.path {
|
||||
let pathBoundingBox = path.bounds
|
||||
if self.bounds.intersects(pathBoundingBox) {
|
||||
for element in linePath.elements {
|
||||
if case .moveTo = element.type {
|
||||
if path.contains(element.startPoint.location.offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if path.contains(element.startPoint.location.offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
if path.contains(element.endPoint.location.offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
if case .cubicCurve = element.type {
|
||||
if path.contains(element.controlPoints[0].offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
if path.contains(element.controlPoints[1].offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
} else if case .quadCurve = element.type {
|
||||
if path.contains(element.controlPoints[0].offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, arrow: Bool) {
|
||||
required init(drawingSize: CGSize, lineWidth: CGFloat) {
|
||||
self.uuid = UUID()
|
||||
self.drawingSize = drawingSize
|
||||
self.color = color
|
||||
self.lineWidth = lineWidth
|
||||
self.arrow = arrow
|
||||
|
||||
let minLineWidth = max(1.0, max(drawingSize.width, drawingSize.height) * 0.003)
|
||||
let maxLineWidth = max(10.0, max(drawingSize.width, drawingSize.height) * 0.09)
|
||||
@ -487,9 +393,31 @@ final class BlurTool: DrawingElement {
|
||||
self.renderLineWidth = lineWidth
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case uuid
|
||||
case drawingSize
|
||||
case renderLineWidth
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.uuid = try container.decode(UUID.self, forKey: .uuid)
|
||||
self.drawingSize = try container.decode(CGSize.self, forKey: .drawingSize)
|
||||
self.renderLineWidth = try container.decode(CGFloat.self, forKey: .renderLineWidth)
|
||||
// self.points = try container.decode([CGPoint].self, forKey: .points)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.uuid, forKey: .uuid)
|
||||
try container.encode(self.drawingSize, forKey: .drawingSize)
|
||||
try container.encode(self.renderLineWidth, forKey: .renderLineWidth)
|
||||
// try container.encode(self.points, forKey: .points)
|
||||
}
|
||||
|
||||
func setupRenderLayer() -> DrawingRenderLayer? {
|
||||
let layer = RenderLayer()
|
||||
layer.setup(size: self.drawingSize, color: self.color, lineWidth: self.renderLineWidth, image: self.getFullImage())
|
||||
layer.setup(size: self.drawingSize, lineWidth: self.renderLineWidth, image: self.getFullImage())
|
||||
self.currentRenderLayer = layer
|
||||
return layer
|
||||
}
|
||||
@ -523,14 +451,14 @@ final class BlurTool: DrawingElement {
|
||||
}
|
||||
|
||||
|
||||
final class EraserTool: DrawingElement {
|
||||
final class EraserTool: DrawingElement, Codable {
|
||||
class RenderLayer: SimpleLayer, DrawingRenderLayer {
|
||||
var lineWidth: CGFloat = 0.0
|
||||
|
||||
let blurLayer = SimpleLayer()
|
||||
let fillLayer = SimpleShapeLayer()
|
||||
|
||||
func setup(size: CGSize, color: DrawingColor, lineWidth: CGFloat, image: UIImage?) {
|
||||
func setup(size: CGSize, lineWidth: CGFloat, image: UIImage?) {
|
||||
self.contentsScale = 1.0
|
||||
self.lineWidth = lineWidth
|
||||
|
||||
@ -563,15 +491,10 @@ final class EraserTool: DrawingElement {
|
||||
|
||||
var getFullImage: () -> UIImage? = { return nil }
|
||||
|
||||
let uuid = UUID()
|
||||
|
||||
let uuid: UUID
|
||||
let drawingSize: CGSize
|
||||
let color: DrawingColor
|
||||
let lineWidth: CGFloat
|
||||
let arrow: Bool
|
||||
|
||||
var path: BezierPath?
|
||||
var boundingBox: CGRect?
|
||||
|
||||
var renderPath: CGPath?
|
||||
let renderLineWidth: CGFloat
|
||||
@ -580,70 +503,9 @@ final class EraserTool: DrawingElement {
|
||||
|
||||
private var currentRenderLayer: DrawingRenderLayer?
|
||||
|
||||
var bounds: CGRect {
|
||||
return self.path?.path.bounds.offsetBy(dx: self.translation.x, dy: self.translation.y) ?? .zero
|
||||
}
|
||||
|
||||
var points: [Polyline.Point] {
|
||||
guard let linePath = self.path else {
|
||||
return []
|
||||
}
|
||||
var points: [Polyline.Point] = []
|
||||
for element in linePath.elements {
|
||||
if case .moveTo = element.type {
|
||||
points.append(element.startPoint.offsetBy(self.translation))
|
||||
} else {
|
||||
points.append(element.endPoint.offsetBy(self.translation))
|
||||
}
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
func containsPoint(_ point: CGPoint) -> Bool {
|
||||
return false
|
||||
// return self.renderPath?.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y))) ?? false
|
||||
}
|
||||
|
||||
func hasPointsInsidePath(_ path: UIBezierPath) -> Bool {
|
||||
if let linePath = self.path {
|
||||
let pathBoundingBox = path.bounds
|
||||
if self.bounds.intersects(pathBoundingBox) {
|
||||
for element in linePath.elements {
|
||||
if case .moveTo = element.type {
|
||||
if path.contains(element.startPoint.location.offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if path.contains(element.startPoint.location.offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
if path.contains(element.endPoint.location.offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
if case .cubicCurve = element.type {
|
||||
if path.contains(element.controlPoints[0].offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
if path.contains(element.controlPoints[1].offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
} else if case .quadCurve = element.type {
|
||||
if path.contains(element.controlPoints[0].offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, arrow: Bool) {
|
||||
required init(drawingSize: CGSize, lineWidth: CGFloat) {
|
||||
self.uuid = UUID()
|
||||
self.drawingSize = drawingSize
|
||||
self.color = color
|
||||
self.lineWidth = lineWidth
|
||||
self.arrow = arrow
|
||||
|
||||
let minLineWidth = max(1.0, max(drawingSize.width, drawingSize.height) * 0.003)
|
||||
let maxLineWidth = max(10.0, max(drawingSize.width, drawingSize.height) * 0.09)
|
||||
@ -652,9 +514,31 @@ final class EraserTool: DrawingElement {
|
||||
self.renderLineWidth = lineWidth
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case uuid
|
||||
case drawingSize
|
||||
case renderLineWidth
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.uuid = try container.decode(UUID.self, forKey: .uuid)
|
||||
self.drawingSize = try container.decode(CGSize.self, forKey: .drawingSize)
|
||||
self.renderLineWidth = try container.decode(CGFloat.self, forKey: .renderLineWidth)
|
||||
// self.points = try container.decode([CGPoint].self, forKey: .points)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.uuid, forKey: .uuid)
|
||||
try container.encode(self.drawingSize, forKey: .drawingSize)
|
||||
try container.encode(self.renderLineWidth, forKey: .renderLineWidth)
|
||||
// try container.encode(self.points, forKey: .points)
|
||||
}
|
||||
|
||||
func setupRenderLayer() -> DrawingRenderLayer? {
|
||||
let layer = RenderLayer()
|
||||
layer.setup(size: self.drawingSize, color: self.color, lineWidth: self.renderLineWidth, image: self.getFullImage())
|
||||
layer.setup(size: self.drawingSize, lineWidth: self.renderLineWidth, image: self.getFullImage())
|
||||
self.currentRenderLayer = layer
|
||||
return layer
|
||||
}
|
||||
@ -687,105 +571,105 @@ final class EraserTool: DrawingElement {
|
||||
}
|
||||
}
|
||||
|
||||
//enum CodableDrawingElement {
|
||||
// case pen(PenTool)
|
||||
// case marker(MarkerTool)
|
||||
// case neon(NeonTool)
|
||||
// case eraser(EraserTool)
|
||||
// case blur(BlurTool)
|
||||
// case fill(FillTool)
|
||||
//
|
||||
// init?(element: DrawingElement) {
|
||||
// if let element = element as? PenTool {
|
||||
// self = .pen(element)
|
||||
// } else if let element = element as? MarkerTool {
|
||||
// self = .marker(element)
|
||||
// } else if let element = element as? NeonTool {
|
||||
// self = .neon(element)
|
||||
// } else if let element = element as? EraserTool {
|
||||
// self = .eraser(element)
|
||||
// } else if let element = element as? BlurTool {
|
||||
// self = .blur(element)
|
||||
// } else if let element = element as? FillTool {
|
||||
// self = .fill(element)
|
||||
// } else {
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var entity: DrawingElement {
|
||||
// switch self {
|
||||
// case let .pen(element):
|
||||
// return element
|
||||
// case let .marker(element):
|
||||
// return element
|
||||
// case let .neon(element):
|
||||
// return element
|
||||
// case let .eraser(element):
|
||||
// return element
|
||||
// case let .blur(element):
|
||||
// return element
|
||||
// case let .fill(element):
|
||||
// return element
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension CodableDrawingElement: Codable {
|
||||
// private enum CodingKeys: String, CodingKey {
|
||||
// case type
|
||||
// case element
|
||||
// }
|
||||
//
|
||||
// private enum ElementType: Int, Codable {
|
||||
// case pen
|
||||
// case marker
|
||||
// case neon
|
||||
// case eraser
|
||||
// case blur
|
||||
// case fill
|
||||
// }
|
||||
//
|
||||
// init(from decoder: Decoder) throws {
|
||||
// let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
// let type = try container.decode(ElementType.self, forKey: .type)
|
||||
// switch type {
|
||||
// case .pen:
|
||||
// self = .pen(try container.decode(PenTool.self, forKey: .element))
|
||||
// case .marker:
|
||||
// self = .marker(try container.decode(MarkerTool.self, forKey: .element))
|
||||
// case .neon:
|
||||
// self = .neon(try container.decode(NeonTool.self, forKey: .element))
|
||||
// case .eraser:
|
||||
// self = .eraser(try container.decode(EraserTool.self, forKey: .element))
|
||||
// case .blur:
|
||||
// self = .blur(try container.decode(BlurTool.self, forKey: .element))
|
||||
// case .fill:
|
||||
// self = .fill(try container.decode(FillTool.self, forKey: .element))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func encode(to encoder: Encoder) throws {
|
||||
// var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
// switch self {
|
||||
// case let .pen(payload):
|
||||
// try container.encode(ElementType.pen, forKey: .type)
|
||||
// try container.encode(payload, forKey: .element)
|
||||
// case let .marker(payload):
|
||||
// try container.encode(ElementType.marker, forKey: .type)
|
||||
// try container.encode(payload, forKey: .element)
|
||||
// case let .neon(payload):
|
||||
// try container.encode(ElementType.neon, forKey: .type)
|
||||
// try container.encode(payload, forKey: .element)
|
||||
// case let .eraser(payload):
|
||||
// try container.encode(ElementType.eraser, forKey: .type)
|
||||
// try container.encode(payload, forKey: .element)
|
||||
// case let .blur(payload):
|
||||
// try container.encode(ElementType.blur, forKey: .type)
|
||||
// try container.encode(payload, forKey: .element)
|
||||
// case let .fill(payload):
|
||||
// try container.encode(ElementType.fill, forKey: .type)
|
||||
// try container.encode(payload, forKey: .element)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
enum CodableDrawingElement {
|
||||
case pen(PenTool)
|
||||
case marker(MarkerTool)
|
||||
case neon(NeonTool)
|
||||
case eraser(EraserTool)
|
||||
case blur(BlurTool)
|
||||
case fill(FillTool)
|
||||
|
||||
init?(element: DrawingElement) {
|
||||
if let element = element as? PenTool {
|
||||
self = .pen(element)
|
||||
} else if let element = element as? MarkerTool {
|
||||
self = .marker(element)
|
||||
} else if let element = element as? NeonTool {
|
||||
self = .neon(element)
|
||||
} else if let element = element as? EraserTool {
|
||||
self = .eraser(element)
|
||||
} else if let element = element as? BlurTool {
|
||||
self = .blur(element)
|
||||
} else if let element = element as? FillTool {
|
||||
self = .fill(element)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var entity: DrawingElement {
|
||||
switch self {
|
||||
case let .pen(element):
|
||||
return element
|
||||
case let .marker(element):
|
||||
return element
|
||||
case let .neon(element):
|
||||
return element
|
||||
case let .eraser(element):
|
||||
return element
|
||||
case let .blur(element):
|
||||
return element
|
||||
case let .fill(element):
|
||||
return element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CodableDrawingElement: Codable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case element
|
||||
}
|
||||
|
||||
private enum ElementType: Int, Codable {
|
||||
case pen
|
||||
case marker
|
||||
case neon
|
||||
case eraser
|
||||
case blur
|
||||
case fill
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let type = try container.decode(ElementType.self, forKey: .type)
|
||||
switch type {
|
||||
case .pen:
|
||||
self = .pen(try container.decode(PenTool.self, forKey: .element))
|
||||
case .marker:
|
||||
self = .marker(try container.decode(MarkerTool.self, forKey: .element))
|
||||
case .neon:
|
||||
self = .neon(try container.decode(NeonTool.self, forKey: .element))
|
||||
case .eraser:
|
||||
self = .eraser(try container.decode(EraserTool.self, forKey: .element))
|
||||
case .blur:
|
||||
self = .blur(try container.decode(BlurTool.self, forKey: .element))
|
||||
case .fill:
|
||||
self = .fill(try container.decode(FillTool.self, forKey: .element))
|
||||
}
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
switch self {
|
||||
case let .pen(payload):
|
||||
try container.encode(ElementType.pen, forKey: .type)
|
||||
try container.encode(payload, forKey: .element)
|
||||
case let .marker(payload):
|
||||
try container.encode(ElementType.marker, forKey: .type)
|
||||
try container.encode(payload, forKey: .element)
|
||||
case let .neon(payload):
|
||||
try container.encode(ElementType.neon, forKey: .type)
|
||||
try container.encode(payload, forKey: .element)
|
||||
case let .eraser(payload):
|
||||
try container.encode(ElementType.eraser, forKey: .type)
|
||||
try container.encode(payload, forKey: .element)
|
||||
case let .blur(payload):
|
||||
try container.encode(ElementType.blur, forKey: .type)
|
||||
try container.encode(payload, forKey: .element)
|
||||
case let .fill(payload):
|
||||
try container.encode(ElementType.fill, forKey: .type)
|
||||
try container.encode(payload, forKey: .element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,18 +9,8 @@ import ImageBlur
|
||||
|
||||
protocol DrawingElement: AnyObject {
|
||||
var uuid: UUID { get }
|
||||
var bounds: CGRect { get }
|
||||
var points: [Polyline.Point] { get }
|
||||
|
||||
var translation: CGPoint { get set }
|
||||
|
||||
var renderLineWidth: CGFloat { get }
|
||||
|
||||
func containsPoint(_ point: CGPoint) -> Bool
|
||||
func hasPointsInsidePath(_ path: UIBezierPath) -> Bool
|
||||
|
||||
init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, arrow: Bool)
|
||||
|
||||
|
||||
func setupRenderLayer() -> DrawingRenderLayer?
|
||||
func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState)
|
||||
|
||||
@ -195,155 +185,120 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if case .lasso = strongSelf.tool {
|
||||
if case let .smoothCurve(bezierPath) = path {
|
||||
let scale = strongSelf.bounds.width / strongSelf.imageSize.width
|
||||
|
||||
switch state {
|
||||
case .began:
|
||||
strongSelf.lassoView.setup(scale: scale)
|
||||
strongSelf.lassoView.updatePath(bezierPath)
|
||||
case .changed:
|
||||
strongSelf.lassoView.updatePath(bezierPath)
|
||||
case .ended:
|
||||
let closedPath = bezierPath.closedCopy()
|
||||
|
||||
var selectedElements: [DrawingElement] = []
|
||||
var selectedPoints: [CGPoint] = []
|
||||
var maxLineWidth: CGFloat = 0.0
|
||||
for element in strongSelf.elements {
|
||||
if element.hasPointsInsidePath(closedPath.path) {
|
||||
maxLineWidth = max(maxLineWidth, element.renderLineWidth)
|
||||
selectedElements.append(element)
|
||||
selectedPoints.append(contentsOf: element.points.map { $0.location })
|
||||
}
|
||||
}
|
||||
|
||||
if selectedPoints.count > 0 {
|
||||
strongSelf.lassoView.apply(scale: scale, points: selectedPoints, selectedElements: selectedElements.map { $0.uuid }, expand: maxLineWidth)
|
||||
} else {
|
||||
strongSelf.lassoView.reset()
|
||||
}
|
||||
case .cancelled:
|
||||
strongSelf.lassoView.reset()
|
||||
}
|
||||
switch state {
|
||||
case .began:
|
||||
strongSelf.isDrawing = true
|
||||
strongSelf.previousStrokePoint = nil
|
||||
strongSelf.drawingGestureStartTimestamp = CACurrentMediaTime()
|
||||
|
||||
if strongSelf.uncommitedElement != nil {
|
||||
strongSelf.finishDrawing()
|
||||
}
|
||||
} else {
|
||||
switch state {
|
||||
case .began:
|
||||
strongSelf.isDrawing = true
|
||||
strongSelf.previousStrokePoint = nil
|
||||
strongSelf.drawingGestureStartTimestamp = CACurrentMediaTime()
|
||||
|
||||
if strongSelf.uncommitedElement != nil {
|
||||
strongSelf.finishDrawing()
|
||||
|
||||
guard let newElement = strongSelf.prepareNewElement() else {
|
||||
return
|
||||
}
|
||||
|
||||
if newElement is MarkerTool {
|
||||
self?.metalView.isHidden = false
|
||||
}
|
||||
|
||||
if let renderLayer = newElement.setupRenderLayer() {
|
||||
if let currentDrawingLayer = strongSelf.currentDrawingLayer {
|
||||
strongSelf.currentDrawingLayer = nil
|
||||
currentDrawingLayer.removeFromSuperlayer()
|
||||
}
|
||||
|
||||
guard let newElement = strongSelf.prepareNewElement() else {
|
||||
return
|
||||
}
|
||||
|
||||
if newElement is MarkerTool {
|
||||
self?.metalView.isHidden = false
|
||||
}
|
||||
|
||||
if let renderLayer = newElement.setupRenderLayer() {
|
||||
if let currentDrawingLayer = strongSelf.currentDrawingLayer {
|
||||
strongSelf.currentDrawingLayer = nil
|
||||
currentDrawingLayer.removeFromSuperlayer()
|
||||
strongSelf.currentDrawingView.layer.addSublayer(renderLayer)
|
||||
strongSelf.currentDrawingLayer = renderLayer
|
||||
}
|
||||
newElement.updatePath(path, state: state)
|
||||
strongSelf.uncommitedElement = newElement
|
||||
strongSelf.updateInternalState()
|
||||
case .changed:
|
||||
strongSelf.uncommitedElement?.updatePath(path, state: state)
|
||||
|
||||
if case let .polyline(line) = path, let lastPoint = line.points.last {
|
||||
if let previousStrokePoint = strongSelf.previousStrokePoint, line.points.count > 10 {
|
||||
let currentTimestamp = CACurrentMediaTime()
|
||||
if lastPoint.location.distance(to: previousStrokePoint) > 10.0 {
|
||||
strongSelf.previousStrokePoint = lastPoint.location
|
||||
|
||||
strongSelf.strokeRecognitionTimer?.invalidate()
|
||||
strongSelf.strokeRecognitionTimer = nil
|
||||
}
|
||||
strongSelf.currentDrawingView.layer.addSublayer(renderLayer)
|
||||
strongSelf.currentDrawingLayer = renderLayer
|
||||
}
|
||||
newElement.updatePath(path, state: state)
|
||||
strongSelf.uncommitedElement = newElement
|
||||
strongSelf.updateInternalState()
|
||||
case .changed:
|
||||
strongSelf.uncommitedElement?.updatePath(path, state: state)
|
||||
|
||||
if case let .polyline(line) = path, let lastPoint = line.points.last {
|
||||
if let previousStrokePoint = strongSelf.previousStrokePoint, line.points.count > 10 {
|
||||
let currentTimestamp = CACurrentMediaTime()
|
||||
if lastPoint.location.distance(to: previousStrokePoint) > 10.0 {
|
||||
strongSelf.previousStrokePoint = lastPoint.location
|
||||
|
||||
strongSelf.strokeRecognitionTimer?.invalidate()
|
||||
strongSelf.strokeRecognitionTimer = nil
|
||||
}
|
||||
|
||||
if strongSelf.strokeRecognitionTimer == nil, let startTimestamp = strongSelf.drawingGestureStartTimestamp, currentTimestamp - startTimestamp < 3.0 {
|
||||
strongSelf.strokeRecognitionTimer = SwiftSignalKit.Timer(timeout: 0.85, repeat: false, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let previousStrokePoint = strongSelf.previousStrokePoint, lastPoint.location.distance(to: previousStrokePoint) <= 10.0 {
|
||||
let strokeRecognizer = Unistroke(points: line.points.map { $0.location })
|
||||
if let template = strokeRecognizer.match(templates: strongSelf.loadedTemplates, minThreshold: 0.5) {
|
||||
let edges = line.bounds
|
||||
let bounds = CGRect(origin: edges.origin, size: CGSize(width: edges.width - edges.minX, height: edges.height - edges.minY))
|
||||
|
||||
var entity: DrawingEntity?
|
||||
if template == "shape_rectangle" {
|
||||
let shapeEntity = DrawingSimpleShapeEntity(shapeType: .rectangle, drawType: .stroke, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize)
|
||||
shapeEntity.referenceDrawingSize = strongSelf.imageSize
|
||||
shapeEntity.position = bounds.center
|
||||
shapeEntity.size = CGSize(width: bounds.size.width * 1.1, height: bounds.size.height * 1.1)
|
||||
entity = shapeEntity
|
||||
} else if template == "shape_circle" {
|
||||
let shapeEntity = DrawingSimpleShapeEntity(shapeType: .ellipse, drawType: .stroke, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize)
|
||||
shapeEntity.referenceDrawingSize = strongSelf.imageSize
|
||||
shapeEntity.position = bounds.center
|
||||
shapeEntity.size = CGSize(width: bounds.size.width * 1.1, height: bounds.size.height * 1.1)
|
||||
entity = shapeEntity
|
||||
} else if template == "shape_star" {
|
||||
let shapeEntity = DrawingSimpleShapeEntity(shapeType: .star, drawType: .stroke, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize)
|
||||
shapeEntity.referenceDrawingSize = strongSelf.imageSize
|
||||
shapeEntity.position = bounds.center
|
||||
shapeEntity.size = CGSize(width: max(bounds.width, bounds.height) * 1.1, height: max(bounds.width, bounds.height) * 1.1)
|
||||
entity = shapeEntity
|
||||
} else if template == "shape_arrow" {
|
||||
let arrowEntity = DrawingVectorEntity(type: .oneSidedArrow, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize)
|
||||
arrowEntity.referenceDrawingSize = strongSelf.imageSize
|
||||
arrowEntity.start = line.points.first?.location ?? .zero
|
||||
arrowEntity.end = line.points[line.points.count - 4].location
|
||||
entity = arrowEntity
|
||||
}
|
||||
|
||||
if let entity = entity {
|
||||
strongSelf.entitiesView?.add(entity)
|
||||
strongSelf.entitiesView?.selectEntity(entity)
|
||||
strongSelf.cancelDrawing()
|
||||
strongSelf.drawingGesturePipeline?.gestureRecognizer?.isEnabled = false
|
||||
strongSelf.drawingGesturePipeline?.gestureRecognizer?.isEnabled = true
|
||||
}
|
||||
|
||||
if strongSelf.strokeRecognitionTimer == nil, let startTimestamp = strongSelf.drawingGestureStartTimestamp, currentTimestamp - startTimestamp < 3.0 {
|
||||
strongSelf.strokeRecognitionTimer = SwiftSignalKit.Timer(timeout: 0.85, repeat: false, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let previousStrokePoint = strongSelf.previousStrokePoint, lastPoint.location.distance(to: previousStrokePoint) <= 10.0 {
|
||||
let strokeRecognizer = Unistroke(points: line.points.map { $0.location })
|
||||
if let template = strokeRecognizer.match(templates: strongSelf.loadedTemplates, minThreshold: 0.5) {
|
||||
let edges = line.bounds
|
||||
let bounds = CGRect(origin: edges.origin, size: CGSize(width: edges.width - edges.minX, height: edges.height - edges.minY))
|
||||
|
||||
var entity: DrawingEntity?
|
||||
if template == "shape_rectangle" {
|
||||
let shapeEntity = DrawingSimpleShapeEntity(shapeType: .rectangle, drawType: .stroke, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize)
|
||||
shapeEntity.referenceDrawingSize = strongSelf.imageSize
|
||||
shapeEntity.position = bounds.center
|
||||
shapeEntity.size = CGSize(width: bounds.size.width * 1.1, height: bounds.size.height * 1.1)
|
||||
entity = shapeEntity
|
||||
} else if template == "shape_circle" {
|
||||
let shapeEntity = DrawingSimpleShapeEntity(shapeType: .ellipse, drawType: .stroke, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize)
|
||||
shapeEntity.referenceDrawingSize = strongSelf.imageSize
|
||||
shapeEntity.position = bounds.center
|
||||
shapeEntity.size = CGSize(width: bounds.size.width * 1.1, height: bounds.size.height * 1.1)
|
||||
entity = shapeEntity
|
||||
} else if template == "shape_star" {
|
||||
let shapeEntity = DrawingSimpleShapeEntity(shapeType: .star, drawType: .stroke, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize)
|
||||
shapeEntity.referenceDrawingSize = strongSelf.imageSize
|
||||
shapeEntity.position = bounds.center
|
||||
shapeEntity.size = CGSize(width: max(bounds.width, bounds.height) * 1.1, height: max(bounds.width, bounds.height) * 1.1)
|
||||
entity = shapeEntity
|
||||
} else if template == "shape_arrow" {
|
||||
let arrowEntity = DrawingVectorEntity(type: .oneSidedArrow, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize)
|
||||
arrowEntity.referenceDrawingSize = strongSelf.imageSize
|
||||
arrowEntity.start = line.points.first?.location ?? .zero
|
||||
arrowEntity.end = line.points[line.points.count - 4].location
|
||||
entity = arrowEntity
|
||||
}
|
||||
|
||||
if let entity = entity {
|
||||
strongSelf.entitiesView?.add(entity)
|
||||
strongSelf.entitiesView?.selectEntity(entity)
|
||||
strongSelf.cancelDrawing()
|
||||
strongSelf.drawingGesturePipeline?.gestureRecognizer?.isEnabled = false
|
||||
strongSelf.drawingGesturePipeline?.gestureRecognizer?.isEnabled = true
|
||||
}
|
||||
}
|
||||
strongSelf.strokeRecognitionTimer?.invalidate()
|
||||
strongSelf.strokeRecognitionTimer = nil
|
||||
}, queue: Queue.mainQueue())
|
||||
strongSelf.strokeRecognitionTimer?.start()
|
||||
}
|
||||
} else {
|
||||
strongSelf.previousStrokePoint = lastPoint.location
|
||||
}
|
||||
strongSelf.strokeRecognitionTimer?.invalidate()
|
||||
strongSelf.strokeRecognitionTimer = nil
|
||||
}, queue: Queue.mainQueue())
|
||||
strongSelf.strokeRecognitionTimer?.start()
|
||||
}
|
||||
} else {
|
||||
strongSelf.previousStrokePoint = lastPoint.location
|
||||
}
|
||||
|
||||
case .ended:
|
||||
strongSelf.isDrawing = false
|
||||
strongSelf.strokeRecognitionTimer?.invalidate()
|
||||
strongSelf.strokeRecognitionTimer = nil
|
||||
strongSelf.uncommitedElement?.updatePath(path, state: state)
|
||||
Queue.mainQueue().after(0.05) {
|
||||
strongSelf.finishDrawing()
|
||||
}
|
||||
strongSelf.updateInternalState()
|
||||
case .cancelled:
|
||||
strongSelf.isDrawing = false
|
||||
strongSelf.strokeRecognitionTimer?.invalidate()
|
||||
strongSelf.strokeRecognitionTimer = nil
|
||||
strongSelf.cancelDrawing()
|
||||
strongSelf.updateInternalState()
|
||||
}
|
||||
|
||||
case .ended:
|
||||
strongSelf.isDrawing = false
|
||||
strongSelf.strokeRecognitionTimer?.invalidate()
|
||||
strongSelf.strokeRecognitionTimer = nil
|
||||
strongSelf.uncommitedElement?.updatePath(path, state: state)
|
||||
Queue.mainQueue().after(0.05) {
|
||||
strongSelf.finishDrawing()
|
||||
}
|
||||
strongSelf.updateInternalState()
|
||||
case .cancelled:
|
||||
strongSelf.isDrawing = false
|
||||
strongSelf.strokeRecognitionTimer?.invalidate()
|
||||
strongSelf.strokeRecognitionTimer = nil
|
||||
strongSelf.cancelDrawing()
|
||||
strongSelf.updateInternalState()
|
||||
}
|
||||
}
|
||||
self.drawingGesturePipeline = drawingGesturePipeline
|
||||
@ -404,6 +359,23 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
||||
self.strokeRecognitionTimer?.invalidate()
|
||||
}
|
||||
|
||||
public func setup(withDrawing drawingData: Data!) {
|
||||
|
||||
}
|
||||
|
||||
var drawingData: Data? {
|
||||
guard !self.elements.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let codableElements = self.elements.compactMap({ CodableDrawingElement(element: $0) })
|
||||
if let data = try? JSONEncoder().encode(codableElements) {
|
||||
return data
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if gestureRecognizer === self.longPressGestureRecognizer, !self.lassoView.isHidden {
|
||||
return false
|
||||
@ -418,6 +390,10 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
||||
private var longPressTimer: SwiftSignalKit.Timer?
|
||||
private var fillCircleLayer: CALayer?
|
||||
@objc func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||
guard ![.eraser, .blur].contains(self.tool) else {
|
||||
return
|
||||
}
|
||||
|
||||
let location = gestureRecognizer.location(in: self)
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
@ -429,7 +405,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
||||
if let strongSelf = self {
|
||||
strongSelf.cancelDrawing()
|
||||
|
||||
let newElement = FillTool(drawingSize: strongSelf.imageSize, color: strongSelf.toolColor, lineWidth: 0.0, arrow: false)
|
||||
let newElement = FillTool(drawingSize: strongSelf.imageSize, color: strongSelf.toolColor)
|
||||
strongSelf.uncommitedElement = newElement
|
||||
strongSelf.finishDrawing()
|
||||
}
|
||||
@ -551,7 +527,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
||||
|
||||
self.updateInternalState()
|
||||
}
|
||||
if let uncommitedElement = self.uncommitedElement as? PenTool, uncommitedElement.arrow {
|
||||
if let uncommitedElement = self.uncommitedElement as? PenTool, uncommitedElement.hasArrow {
|
||||
uncommitedElement.finishArrow({
|
||||
complete(true)
|
||||
})
|
||||
@ -779,15 +755,14 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
||||
drawingSize: self.imageSize,
|
||||
color: self.toolColor,
|
||||
lineWidth: self.toolBrushSize * scale,
|
||||
arrow: self.toolHasArrow
|
||||
hasArrow: self.toolHasArrow
|
||||
)
|
||||
element = penTool
|
||||
case .marker:
|
||||
let markerTool = MarkerTool(
|
||||
drawingSize: self.imageSize,
|
||||
color: self.toolColor,
|
||||
lineWidth: self.toolBrushSize * scale,
|
||||
arrow: self.toolHasArrow
|
||||
lineWidth: self.toolBrushSize * scale
|
||||
)
|
||||
markerTool.metalView = self.metalView
|
||||
element = markerTool
|
||||
@ -795,15 +770,13 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
||||
element = NeonTool(
|
||||
drawingSize: self.imageSize,
|
||||
color: self.toolColor,
|
||||
lineWidth: self.toolBrushSize * scale,
|
||||
arrow: self.toolHasArrow
|
||||
lineWidth: self.toolBrushSize * scale
|
||||
)
|
||||
case .blur:
|
||||
let blurTool = BlurTool(
|
||||
drawingSize: self.imageSize,
|
||||
color: self.toolColor,
|
||||
lineWidth: self.toolBrushSize * scale,
|
||||
arrow: false)
|
||||
lineWidth: self.toolBrushSize * scale
|
||||
)
|
||||
blurTool.getFullImage = { [weak self] in
|
||||
return self?.preparredEraserImage
|
||||
}
|
||||
@ -811,9 +784,8 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
|
||||
case .eraser:
|
||||
let eraserTool = EraserTool(
|
||||
drawingSize: self.imageSize,
|
||||
color: self.toolColor,
|
||||
lineWidth: self.toolBrushSize * scale,
|
||||
arrow: false)
|
||||
lineWidth: self.toolBrushSize * scale
|
||||
)
|
||||
eraserTool.getFullImage = { [weak self] in
|
||||
return self?.preparredEraserImage
|
||||
}
|
||||
|
@ -2,9 +2,13 @@ import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
|
||||
final class PenTool: DrawingElement {
|
||||
final class PenTool: DrawingElement, Codable {
|
||||
class RenderLayer: SimpleLayer, DrawingRenderLayer {
|
||||
var segmentsCount = 0
|
||||
private weak var element: PenTool?
|
||||
|
||||
private var segmentsCount = 0
|
||||
private var velocity: CGFloat?
|
||||
private var previousRect: CGRect?
|
||||
|
||||
var displayLink: ConstantDisplayLinkAnimator?
|
||||
func setup(size: CGSize) {
|
||||
@ -16,13 +20,13 @@ final class PenTool: DrawingElement {
|
||||
|
||||
self.displayLink = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let line = strongSelf.line, strongSelf.segmentsCount < line.count, let velocity = strongSelf.velocity {
|
||||
if let element = strongSelf.element, strongSelf.segmentsCount < element.segments.count, let velocity = strongSelf.velocity {
|
||||
let delta = max(9, Int(velocity / 100.0))
|
||||
let start = strongSelf.segmentsCount
|
||||
strongSelf.segmentsCount = min(strongSelf.segmentsCount + delta, line.count)
|
||||
strongSelf.segmentsCount = min(strongSelf.segmentsCount + delta, element.segments.count)
|
||||
|
||||
let rect = line.rect(from: start, to: strongSelf.segmentsCount)
|
||||
strongSelf.setNeedsDisplay(rect.insetBy(dx: -50.0, dy: -50.0))
|
||||
let rect = element.boundingRect(from: start, to: strongSelf.segmentsCount)
|
||||
strongSelf.setNeedsDisplay(rect.insetBy(dx: -80.0, dy: -80.0))
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -30,28 +34,24 @@ final class PenTool: DrawingElement {
|
||||
self.displayLink?.isPaused = false
|
||||
}
|
||||
|
||||
|
||||
private var color: UIColor?
|
||||
private var line: StrokeLine?
|
||||
private var velocity: CGFloat?
|
||||
private var previousRect: CGRect?
|
||||
fileprivate func draw(line: StrokeLine, velocity: CGFloat, color: UIColor, rect: CGRect) {
|
||||
self.line = line
|
||||
self.color = color
|
||||
|
||||
fileprivate func draw(element: PenTool, velocity: CGFloat, rect: CGRect) {
|
||||
self.element = element
|
||||
|
||||
self.previousRect = rect
|
||||
if let previous = self.velocity {
|
||||
self.velocity = velocity * 0.4 + previous * 0.6
|
||||
} else {
|
||||
self.velocity = velocity
|
||||
}
|
||||
self.setNeedsDisplay(rect.insetBy(dx: -50.0, dy: -50.0))
|
||||
self.setNeedsDisplay(rect.insetBy(dx: -80.0, dy: -80.0))
|
||||
}
|
||||
|
||||
func animateArrowPaths(leftArrowPath: UIBezierPath, rightArrowPath: UIBezierPath, lineWidth: CGFloat, completion: @escaping () -> Void) {
|
||||
let leftArrowShape = CAShapeLayer()
|
||||
leftArrowShape.path = leftArrowPath.cgPath
|
||||
leftArrowShape.lineWidth = lineWidth
|
||||
leftArrowShape.strokeColor = self.color?.cgColor
|
||||
leftArrowShape.strokeColor = self.element?.color.toCGColor()
|
||||
leftArrowShape.lineCap = .round
|
||||
leftArrowShape.frame = self.bounds
|
||||
self.addSublayer(leftArrowShape)
|
||||
@ -59,7 +59,7 @@ final class PenTool: DrawingElement {
|
||||
let rightArrowShape = CAShapeLayer()
|
||||
rightArrowShape.path = rightArrowPath.cgPath
|
||||
rightArrowShape.lineWidth = lineWidth
|
||||
rightArrowShape.strokeColor = self.color?.cgColor
|
||||
rightArrowShape.strokeColor = self.element?.color.toCGColor()
|
||||
rightArrowShape.lineCap = .round
|
||||
rightArrowShape.frame = self.bounds
|
||||
self.addSublayer(rightArrowShape)
|
||||
@ -74,84 +74,79 @@ final class PenTool: DrawingElement {
|
||||
}
|
||||
|
||||
override func draw(in ctx: CGContext) {
|
||||
self.line?.drawInContext(ctx, upTo: self.segmentsCount)
|
||||
self.element?.drawSegments(in: ctx, upTo: self.segmentsCount)
|
||||
}
|
||||
}
|
||||
|
||||
let uuid = UUID()
|
||||
|
||||
let uuid: UUID
|
||||
let drawingSize: CGSize
|
||||
let color: DrawingColor
|
||||
let lineWidth: CGFloat
|
||||
let arrow: Bool
|
||||
|
||||
var path: Polyline?
|
||||
var boundingBox: CGRect?
|
||||
|
||||
private var renderLine: StrokeLine
|
||||
let renderLineWidth: CGFloat
|
||||
let renderMinLineWidth: CGFloat
|
||||
|
||||
let hasArrow: Bool
|
||||
let renderArrowLength: CGFloat
|
||||
let renderArrowLineWidth: CGFloat
|
||||
|
||||
var didSetupArrow = false
|
||||
|
||||
var arrowLeftPath: UIBezierPath?
|
||||
var arrowLeftPoint: CGPoint?
|
||||
var arrowRightPath: UIBezierPath?
|
||||
var arrowRightPoint: CGPoint?
|
||||
|
||||
var translation = CGPoint()
|
||||
var translation: CGPoint = .zero
|
||||
|
||||
private var currentRenderLayer: DrawingRenderLayer?
|
||||
private weak var currentRenderLayer: DrawingRenderLayer?
|
||||
|
||||
var bounds: CGRect {
|
||||
return self.path?.bounds.offsetBy(dx: self.translation.x, dy: self.translation.y) ?? .zero
|
||||
}
|
||||
|
||||
var points: [Polyline.Point] {
|
||||
guard let linePath = self.path else {
|
||||
return []
|
||||
}
|
||||
var points: [Polyline.Point] = []
|
||||
for point in linePath.points {
|
||||
points.append(point.offsetBy(self.translation))
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
func containsPoint(_ point: CGPoint) -> Bool {
|
||||
return false
|
||||
// return self.renderPath?.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y))) ?? false
|
||||
}
|
||||
|
||||
func hasPointsInsidePath(_ path: UIBezierPath) -> Bool {
|
||||
if let linePath = self.path {
|
||||
let pathBoundingBox = path.bounds
|
||||
if self.bounds.intersects(pathBoundingBox) {
|
||||
for point in linePath.points {
|
||||
if path.contains(point.location.offsetBy(self.translation)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, arrow: Bool) {
|
||||
required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, hasArrow: Bool) {
|
||||
self.uuid = UUID()
|
||||
self.drawingSize = drawingSize
|
||||
self.color = color
|
||||
self.lineWidth = lineWidth
|
||||
self.arrow = arrow
|
||||
self.hasArrow = hasArrow
|
||||
|
||||
let minLineWidth = max(1.0, max(drawingSize.width, drawingSize.height) * 0.002)
|
||||
let maxLineWidth = max(10.0, max(drawingSize.width, drawingSize.height) * 0.07)
|
||||
let lineWidth = minLineWidth + (maxLineWidth - minLineWidth) * lineWidth
|
||||
|
||||
self.renderLineWidth = lineWidth
|
||||
self.renderMinLineWidth = minLineWidth + (lineWidth - minLineWidth) * 0.3
|
||||
self.renderArrowLength = lineWidth * 3.0
|
||||
self.renderArrowLineWidth = lineWidth * 0.8
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case uuid
|
||||
case drawingSize
|
||||
case color
|
||||
case hasArrow
|
||||
|
||||
self.renderLine = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth + (lineWidth - minLineWidth) * 0.3, lineWidth: lineWidth)
|
||||
case renderLineWidth
|
||||
case renderMinLineWidth
|
||||
case renderArrowLength
|
||||
case renderArrowLineWidth
|
||||
|
||||
case renderSegments
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.uuid = try container.decode(UUID.self, forKey: .uuid)
|
||||
self.drawingSize = try container.decode(CGSize.self, forKey: .drawingSize)
|
||||
self.color = try container.decode(DrawingColor.self, forKey: .color)
|
||||
self.hasArrow = try container.decode(Bool.self, forKey: .hasArrow)
|
||||
self.renderLineWidth = try container.decode(CGFloat.self, forKey: .renderLineWidth)
|
||||
self.renderMinLineWidth = try container.decode(CGFloat.self, forKey: .renderMinLineWidth)
|
||||
self.renderArrowLength = try container.decode(CGFloat.self, forKey: .renderArrowLength)
|
||||
self.renderArrowLineWidth = try container.decode(CGFloat.self, forKey: .renderArrowLineWidth)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.uuid, forKey: .uuid)
|
||||
try container.encode(self.drawingSize, forKey: .drawingSize)
|
||||
try container.encode(self.color, forKey: .color)
|
||||
try container.encode(self.hasArrow, forKey: .hasArrow)
|
||||
try container.encode(self.renderLineWidth, forKey: .renderLineWidth)
|
||||
try container.encode(self.renderMinLineWidth, forKey: .renderMinLineWidth)
|
||||
try container.encode(self.renderArrowLength, forKey: .renderArrowLength)
|
||||
try container.encode(self.renderArrowLineWidth, forKey: .renderArrowLineWidth)
|
||||
}
|
||||
|
||||
func finishArrow(_ completion: @escaping () -> Void) {
|
||||
@ -176,70 +171,78 @@ final class PenTool: DrawingElement {
|
||||
guard case let .polyline(line) = path, let point = line.points.last else {
|
||||
return
|
||||
}
|
||||
self.path = line
|
||||
|
||||
|
||||
let filterDistance: CGFloat
|
||||
if point.velocity > 1200 {
|
||||
if point.velocity > 1200.0 {
|
||||
filterDistance = 75.0
|
||||
} else {
|
||||
filterDistance = 35.0
|
||||
}
|
||||
|
||||
if let previousPoint, point.location.distance(to: previousPoint) < filterDistance, state == .changed, self.renderLine.ready {
|
||||
if let previousPoint, point.location.distance(to: previousPoint) < filterDistance, state == .changed, self.segments.count > 0 {
|
||||
return
|
||||
}
|
||||
self.previousPoint = point.location
|
||||
|
||||
let rect = self.renderLine.draw(at: point)
|
||||
if let currentRenderLayer = self.currentRenderLayer as? RenderLayer {
|
||||
currentRenderLayer.draw(line: self.renderLine, velocity: point.velocity, color: self.color.toUIColor(), rect: rect)
|
||||
var velocity = point.velocity
|
||||
if velocity.isZero {
|
||||
velocity = 600.0
|
||||
}
|
||||
|
||||
var effectiveRenderLineWidth = max(self.renderMinLineWidth, min(self.renderLineWidth + 1.0 - (velocity / 180.0), self.renderLineWidth))
|
||||
if let previousRenderLineWidth = self.previousRenderLineWidth {
|
||||
effectiveRenderLineWidth = effectiveRenderLineWidth * 0.2 + previousRenderLineWidth * 0.8
|
||||
}
|
||||
self.previousRenderLineWidth = effectiveRenderLineWidth
|
||||
|
||||
let rect = append(point: Point(position: point.location, width: effectiveRenderLineWidth))
|
||||
|
||||
if let currentRenderLayer = self.currentRenderLayer as? RenderLayer, let rect = rect {
|
||||
currentRenderLayer.draw(element: self, velocity: point.velocity, rect: rect)
|
||||
}
|
||||
|
||||
if state == .ended {
|
||||
if self.arrow {
|
||||
let points = self.path?.points ?? []
|
||||
|
||||
if self.hasArrow {
|
||||
var direction: CGFloat?
|
||||
if points.count > 4 {
|
||||
let p2 = points[points.count - 1].location
|
||||
for i in 1 ..< min(points.count - 2, 12) {
|
||||
let p1 = points[points.count - 1 - i].location
|
||||
if p1.distance(to: p2) > renderArrowLength * 0.5 {
|
||||
if self.smoothPoints.count > 4 {
|
||||
let p2 = self.smoothPoints[self.smoothPoints.count - 1].position
|
||||
for i in 1 ..< min(self.smoothPoints.count - 2, 12) {
|
||||
let p1 = self.smoothPoints[self.smoothPoints.count - 1 - i].position
|
||||
if p1.distance(to: p2) > self.renderArrowLength * 0.5 {
|
||||
direction = p2.angle(to: p1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let point = points.last?.location, let direction {
|
||||
if let point = self.smoothPoints.last?.position, let direction {
|
||||
let arrowLeftPath = UIBezierPath()
|
||||
arrowLeftPath.move(to: point)
|
||||
let leftPoint = point.pointAt(distance: self.renderArrowLength, angle: direction - 0.45)
|
||||
arrowLeftPath.addLine(to: leftPoint)
|
||||
arrowLeftPath.addLine(to: point.pointAt(distance: self.renderArrowLength, angle: direction - 0.45))
|
||||
|
||||
let arrowRightPath = UIBezierPath()
|
||||
arrowRightPath.move(to: point)
|
||||
let rightPoint = point.pointAt(distance: self.renderArrowLength, angle: direction + 0.45)
|
||||
arrowRightPath.addLine(to: rightPoint)
|
||||
arrowRightPath.addLine(to: point.pointAt(distance: self.renderArrowLength, angle: direction + 0.45))
|
||||
|
||||
self.arrowLeftPath = arrowLeftPath
|
||||
self.arrowLeftPoint = leftPoint
|
||||
|
||||
self.arrowRightPath = arrowRightPath
|
||||
self.arrowRightPoint = rightPoint
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func draw(in context: CGContext, size: CGSize) {
|
||||
guard !self.segments.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
context.saveGState()
|
||||
|
||||
context.translateBy(x: self.translation.x, y: self.translation.y)
|
||||
|
||||
context.setShouldAntialias(true)
|
||||
|
||||
self.renderLine.drawInContext(context)
|
||||
self.drawSegments(in: context, upTo: self.segments.count)
|
||||
|
||||
if let arrowLeftPath, let arrowRightPath {
|
||||
context.setStrokeColor(self.color.toCGColor())
|
||||
@ -255,75 +258,37 @@ final class PenTool: DrawingElement {
|
||||
|
||||
context.restoreGState()
|
||||
}
|
||||
}
|
||||
|
||||
private class StrokeLine {
|
||||
struct Segment {
|
||||
|
||||
private struct Segment {
|
||||
let a: CGPoint
|
||||
let b: CGPoint
|
||||
let c: CGPoint
|
||||
let d: CGPoint
|
||||
let abWidth: CGFloat
|
||||
let cdWidth: CGFloat
|
||||
let radius1: CGFloat
|
||||
let radius2: CGFloat
|
||||
let rect: CGRect
|
||||
}
|
||||
|
||||
struct Point {
|
||||
private struct Point {
|
||||
let position: CGPoint
|
||||
let width: CGFloat
|
||||
|
||||
init(position: CGPoint, width: CGFloat) {
|
||||
init(
|
||||
position: CGPoint,
|
||||
width: CGFloat
|
||||
) {
|
||||
self.position = position
|
||||
self.width = width
|
||||
}
|
||||
}
|
||||
|
||||
private(set) var points: [Point] = []
|
||||
private var points: [Point] = []
|
||||
private var smoothPoints: [Point] = []
|
||||
private var segments: [Segment] = []
|
||||
|
||||
private let minLineWidth: CGFloat
|
||||
let lineWidth: CGFloat
|
||||
private var lastWidth: CGFloat?
|
||||
|
||||
var ready = false
|
||||
private var previousRenderLineWidth: CGFloat?
|
||||
|
||||
let color: UIColor
|
||||
|
||||
init(color: UIColor, minLineWidth: CGFloat, lineWidth: CGFloat) {
|
||||
self.color = color
|
||||
self.minLineWidth = minLineWidth
|
||||
self.lineWidth = lineWidth
|
||||
}
|
||||
|
||||
func draw(at point: Polyline.Point) -> CGRect {
|
||||
var velocity = point.velocity
|
||||
if velocity.isZero {
|
||||
velocity = 600.0
|
||||
}
|
||||
let width = extractLineWidth(from: velocity)
|
||||
self.lastWidth = width
|
||||
|
||||
let point = Point(position: point.location, width: width)
|
||||
return appendPoint(point)
|
||||
}
|
||||
|
||||
func drawInContext(_ context: CGContext, upTo: Int? = nil) {
|
||||
self.drawSegments(self.segments, upTo: upTo ?? self.segments.count, inContext: context)
|
||||
}
|
||||
|
||||
func extractLineWidth(from velocity: CGFloat) -> CGFloat {
|
||||
let minValue = self.minLineWidth
|
||||
let maxValue = self.lineWidth
|
||||
|
||||
var width = max(minValue, min(maxValue + 1.0 - (velocity / 180.0), maxValue))
|
||||
if let lastWidth = self.lastWidth {
|
||||
width = width * 0.2 + lastWidth * 0.8
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
func appendPoint(_ point: Point) -> CGRect {
|
||||
private func append(point: Point) -> CGRect? {
|
||||
self.points.append(point)
|
||||
|
||||
guard self.points.count > 2 else { return .null }
|
||||
@ -340,9 +305,11 @@ private class StrokeLine {
|
||||
)
|
||||
|
||||
let lastOldSmoothPoint = smoothPoints.last
|
||||
smoothPoints.append(contentsOf: newSmoothPoints)
|
||||
self.smoothPoints.append(contentsOf: newSmoothPoints)
|
||||
|
||||
guard smoothPoints.count > 1 else { return .null }
|
||||
guard self.smoothPoints.count > 1 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let newSegments: ([Segment], CGRect) = {
|
||||
guard let lastOldSmoothPoint = lastOldSmoothPoint else {
|
||||
@ -350,20 +317,18 @@ private class StrokeLine {
|
||||
}
|
||||
return segments(fromSmoothPoints: [lastOldSmoothPoint] + newSmoothPoints)
|
||||
}()
|
||||
segments.append(contentsOf: newSegments.0)
|
||||
|
||||
self.ready = true
|
||||
|
||||
self.segments.append(contentsOf: newSegments.0)
|
||||
|
||||
return newSegments.1
|
||||
}
|
||||
|
||||
func smoothPoints(fromPoint0 point0: Point, point1: Point, point2: Point) -> [Point] {
|
||||
var smoothPoints = [Point]()
|
||||
private func smoothPoints(fromPoint0 point0: Point, point1: Point, point2: Point) -> [Point] {
|
||||
var smoothPoints: [Point] = []
|
||||
|
||||
let midPoint1 = (point0.position + point1.position) * 0.5
|
||||
let midPoint2 = (point1.position + point2.position) * 0.5
|
||||
|
||||
let segmentDistance = 3.0
|
||||
let segmentDistance: CGFloat = 3.0
|
||||
let distance = midPoint1.distance(to: midPoint2)
|
||||
let numberOfSegments = min(128, max(floor(distance / segmentDistance), 32))
|
||||
|
||||
@ -381,7 +346,33 @@ private class StrokeLine {
|
||||
return smoothPoints
|
||||
}
|
||||
|
||||
func segments(fromSmoothPoints smoothPoints: [Point]) -> ([Segment], CGRect) {
|
||||
fileprivate func boundingRect(from: Int, to: Int) -> CGRect {
|
||||
var minX: CGFloat = .greatestFiniteMagnitude
|
||||
var minY: CGFloat = .greatestFiniteMagnitude
|
||||
var maxX: CGFloat = 0.0
|
||||
var maxY: CGFloat = 0.0
|
||||
|
||||
for i in from ..< to {
|
||||
let segment = self.segments[i]
|
||||
|
||||
if segment.rect.minX < minX {
|
||||
minX = segment.rect.minX
|
||||
}
|
||||
if segment.rect.maxX > maxX {
|
||||
maxX = segment.rect.maxX
|
||||
}
|
||||
if segment.rect.minY < minY {
|
||||
minY = segment.rect.minY
|
||||
}
|
||||
if segment.rect.maxY > maxY {
|
||||
maxY = segment.rect.maxY
|
||||
}
|
||||
}
|
||||
|
||||
return CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)
|
||||
}
|
||||
|
||||
private func segments(fromSmoothPoints smoothPoints: [Point]) -> ([Segment], CGRect) {
|
||||
var segments: [Segment] = []
|
||||
var updateRect = CGRect.null
|
||||
for i in 1 ..< smoothPoints.count {
|
||||
@ -425,65 +416,41 @@ private class StrokeLine {
|
||||
let segmentRect = CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)
|
||||
updateRect = updateRect.union(segmentRect)
|
||||
|
||||
segments.append(Segment(a: a, b: b, c: c, d: d, abWidth: previousWidth, cdWidth: currentWidth, rect: segmentRect))
|
||||
segments.append(Segment(a: a, b: b, c: c, d: d, radius1: previousWidth / 2.0, radius2: currentWidth / 2.0, rect: segmentRect))
|
||||
}
|
||||
return (segments, updateRect)
|
||||
}
|
||||
|
||||
var count: Int {
|
||||
return self.segments.count
|
||||
}
|
||||
|
||||
func rect(from: Int, to: Int) -> CGRect {
|
||||
var minX: CGFloat = .greatestFiniteMagnitude
|
||||
var minY: CGFloat = .greatestFiniteMagnitude
|
||||
var maxX: CGFloat = 0.0
|
||||
var maxY: CGFloat = 0.0
|
||||
private func drawSegments(in context: CGContext, upTo: Int) {
|
||||
context.setStrokeColor(self.color.toCGColor())
|
||||
context.setFillColor(self.color.toCGColor())
|
||||
|
||||
for i in from ..< to {
|
||||
let segment = self.segments[i]
|
||||
|
||||
if segment.rect.minX < minX {
|
||||
minX = segment.rect.minX
|
||||
}
|
||||
if segment.rect.maxX > maxX {
|
||||
maxX = segment.rect.maxX
|
||||
}
|
||||
if segment.rect.minY < minY {
|
||||
minY = segment.rect.minY
|
||||
}
|
||||
if segment.rect.maxY > maxY {
|
||||
maxY = segment.rect.maxY
|
||||
}
|
||||
}
|
||||
|
||||
return CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)
|
||||
}
|
||||
|
||||
func drawSegments(_ segments: [Segment], upTo: Int, inContext context: CGContext) {
|
||||
for i in 0 ..< upTo {
|
||||
let segment = segments[i]
|
||||
let segment = self.segments[i]
|
||||
context.beginPath()
|
||||
|
||||
context.setStrokeColor(color.cgColor)
|
||||
context.setFillColor(color.cgColor)
|
||||
|
||||
|
||||
context.move(to: segment.b)
|
||||
|
||||
let abStartAngle = atan2(segment.b.y - segment.a.y, segment.b.x - segment.a.x)
|
||||
let abStartAngle = atan2(
|
||||
segment.b.y - segment.a.y,
|
||||
segment.b.x - segment.a.x
|
||||
)
|
||||
context.addArc(
|
||||
center: (segment.a + segment.b)/2,
|
||||
radius: segment.abWidth/2,
|
||||
radius: segment.radius1,
|
||||
startAngle: abStartAngle,
|
||||
endAngle: abStartAngle + .pi,
|
||||
clockwise: true
|
||||
)
|
||||
context.addLine(to: segment.c)
|
||||
|
||||
let cdStartAngle = atan2(segment.c.y - segment.d.y, segment.c.x - segment.d.x)
|
||||
let cdStartAngle = atan2(
|
||||
segment.c.y - segment.d.y,
|
||||
segment.c.x - segment.d.x
|
||||
)
|
||||
context.addArc(
|
||||
center: (segment.c + segment.d) / 2,
|
||||
radius: segment.cdWidth/2,
|
||||
radius: segment.radius2,
|
||||
startAngle: cdStartAngle,
|
||||
endAngle: cdStartAngle + .pi,
|
||||
clockwise: true
|
||||
@ -495,3 +462,4 @@ private class StrokeLine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,9 @@ typedef enum {
|
||||
- (void)forceStatusBarAppearanceUpdate;
|
||||
- (bool)prefersLightStatusBar;
|
||||
|
||||
- (void)lockPortrait;
|
||||
- (void)unlockPortrait;
|
||||
|
||||
- (TGNavigationBarPallete *)navigationBarPallete;
|
||||
- (TGMenuSheetPallete *)menuSheetPallete;
|
||||
- (TGMenuSheetPallete *)darkMenuSheetPallete;
|
||||
|
@ -71,6 +71,8 @@
|
||||
|
||||
- (void)updateZoomScale:(CGFloat)scale;
|
||||
|
||||
- (void)setupWithDrawingData:(NSData *)drawingData;
|
||||
|
||||
@end
|
||||
|
||||
@protocol TGPhotoDrawingEntitiesView <NSObject>
|
||||
|
@ -118,7 +118,7 @@ const CGSize TGPhotoPaintingMaxSize = { 2560.0f, 2560.0f };
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
NSLog(@"");
|
||||
[_context unlockPortrait];
|
||||
}
|
||||
|
||||
- (void)loadView
|
||||
@ -209,6 +209,8 @@ const CGSize TGPhotoPaintingMaxSize = { 2560.0f, 2560.0f };
|
||||
[strongSelf->_scrollView setZoomScale:strongSelf->_scrollView.normalZoomScale animated:true];
|
||||
};
|
||||
[_paintingWrapperView addSubview:_drawingView];
|
||||
|
||||
[_drawingView setupWithDrawingData:_photoEditor.paintingData.drawingData];
|
||||
}
|
||||
|
||||
_entitiesView.hasSelectionChanged = ^(bool hasSelection) {
|
||||
@ -471,6 +473,7 @@ const CGSize TGPhotoPaintingMaxSize = { 2560.0f, 2560.0f };
|
||||
#pragma mark - Transitions
|
||||
|
||||
- (void)transitionIn {
|
||||
[_context lockPortrait];
|
||||
// if (self.presentedForAvatarCreation) {
|
||||
// _drawingView.hidden = true;
|
||||
// }
|
||||
|
@ -109,6 +109,19 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func lockPortrait() {
|
||||
if let controller = self.controller as? LegacyController {
|
||||
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
}
|
||||
}
|
||||
|
||||
public func unlockPortrait() {
|
||||
if let controller = self.controller as? LegacyController {
|
||||
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown)
|
||||
}
|
||||
}
|
||||
|
||||
public func keyCommandController() -> TGKeyCommandController! {
|
||||
return nil
|
||||
}
|
||||
|
@ -6839,6 +6839,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}
|
||||
if let topController = self.controller?.navigationController?.topViewController as? ViewController {
|
||||
topController.presentInGlobalOverlay(statusController)
|
||||
} else if let topController = self.controller?.parentController?.topViewController as? ViewController {
|
||||
topController.presentInGlobalOverlay(statusController)
|
||||
} else {
|
||||
self.controller?.presentInGlobalOverlay(statusController)
|
||||
}
|
||||
@ -7014,6 +7016,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}
|
||||
if let topController = self.controller?.navigationController?.topViewController as? ViewController {
|
||||
topController.presentInGlobalOverlay(statusController)
|
||||
} else if let topController = self.controller?.parentController?.topViewController as? ViewController {
|
||||
topController.presentInGlobalOverlay(statusController)
|
||||
} else {
|
||||
self.controller?.presentInGlobalOverlay(statusController)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user