From 729b4d952c0c0790802fd010249469628a3896c2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 22 Dec 2022 20:58:01 +0400 Subject: [PATCH] Drawing improvements --- .../DrawingUI/Sources/DrawingScreen.swift | 3 +- .../DrawingUI/Sources/DrawingTextEntity.swift | 10 +- .../DrawingUI/Sources/DrawingTools.swift | 237 ++---------------- .../DrawingUI/Sources/DrawingView.swift | 142 ++++++++--- submodules/DrawingUI/Sources/PenTool.swift | 149 +++++------ .../Sources/TextSettingsComponent.swift | 1 + .../TGMediaAssetsController.h | 8 +- .../Sources/TGMediaAssetsController.m | 8 +- .../Sources/TGMediaAssetsPickerController.m | 8 +- .../Sources/TGMediaAvatarMenuMixin.m | 40 ++- 10 files changed, 244 insertions(+), 362 deletions(-) diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index 2ff7d85923..50913b526d 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -1423,7 +1423,8 @@ private final class DrawingScreenComponent: CombinedComponent { transition: context.transition ) context.add(textSize - .position(CGPoint(x: sizeSliderVisible ? textSize.size.width / 2.0 : textSize.size.width / 2.0 - 33.0, y: topInset + (context.availableSize.height - topInset - bottomInset) / 2.0)) + .position(CGPoint(x: textSize.size.width / 2.0, y: topInset + (context.availableSize.height - topInset - bottomInset) / 2.0)) + .opacity(sizeSliderVisible ? 1.0 : 0.0) ) let undoButton = undoButton.update( diff --git a/submodules/DrawingUI/Sources/DrawingTextEntity.swift b/submodules/DrawingUI/Sources/DrawingTextEntity.swift index be5e8ec5ef..6ae9a21386 100644 --- a/submodules/DrawingUI/Sources/DrawingTextEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingTextEntity.swift @@ -345,7 +345,7 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { return emojiViewProvider(emoji) }) customEmojiContainerView.isUserInteractionEnabled = false - customEmojiContainerView.center = customEmojiContainerView.center.offsetBy(dx: 0.0, dy: 10.0) + customEmojiContainerView.center = customEmojiContainerView.center self.addSubview(customEmojiContainerView) self.customEmojiContainerView = customEmojiContainerView } @@ -523,9 +523,7 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { override func layoutSubviews() { super.layoutSubviews() - var rect = self.bounds - rect = rect.offsetBy(dx: 0.0, dy: 10.0) // CGRectOffset(rect, 0.0f, 10.0f); - self.textView.frame = rect + self.textView.frame = self.bounds } private var displayFontSize: CGFloat { @@ -672,7 +670,7 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { func getRenderImage() -> UIImage? { let rect = self.bounds UIGraphicsBeginImageContextWithOptions(rect.size, false, 1.0) - self.textView.drawHierarchy(in: rect.offsetBy(dx: 0.0, dy: 10.0), afterScreenUpdates: true) + self.textView.drawHierarchy(in: rect, afterScreenUpdates: true) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image @@ -691,7 +689,7 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { guard let file = emojiAttribute.file else { continue } - let emojiTextPosition = emojiRect.offsetBy(dx: 0.0, dy: 10.0).center.offsetBy(dx: -textSize.width / 2.0, dy: -textSize.height / 2.0) + let emojiTextPosition = emojiRect.center.offsetBy(dx: -textSize.width / 2.0, dy: -textSize.height / 2.0) let entity = DrawingStickerEntity(file: file) entity.referenceDrawingSize = CGSize(width: itemSize * 2.5, height: itemSize * 2.5) diff --git a/submodules/DrawingUI/Sources/DrawingTools.swift b/submodules/DrawingUI/Sources/DrawingTools.swift index b80fc8e714..7121ca9704 100644 --- a/submodules/DrawingUI/Sources/DrawingTools.swift +++ b/submodules/DrawingUI/Sources/DrawingTools.swift @@ -2,10 +2,6 @@ import Foundation import UIKit import Display -protocol DrawingRenderLayer: CALayer { - -} - final class MarkerTool: DrawingElement, Codable { let uuid: UUID @@ -62,6 +58,10 @@ final class MarkerTool: DrawingElement, Codable { try container.encode(self.points, forKey: .points) } + func setupRenderView(screenSize: CGSize) -> DrawingRenderView? { + return nil + } + func setupRenderLayer() -> DrawingRenderLayer? { return nil } @@ -219,6 +219,10 @@ final class NeonTool: DrawingElement, Codable { // try container.encode(self.points, forKey: .points) } + func setupRenderView(screenSize: CGSize) -> DrawingRenderView? { + return nil + } + func setupRenderLayer() -> DrawingRenderLayer? { let layer = RenderLayer() layer.setup(size: self.drawingSize, color: self.color, lineWidth: self.renderLineWidth, strokeWidth: self.renderStrokeWidth, shadowRadius: self.renderShadowRadius) @@ -275,7 +279,6 @@ final class NeonTool: DrawingElement, Codable { } final class FillTool: DrawingElement, Codable { - let uuid: UUID let drawingSize: CGSize @@ -320,6 +323,10 @@ final class FillTool: DrawingElement, Codable { // try container.encode(self.points, forKey: .points) } + func setupRenderView(screenSize: CGSize) -> DrawingRenderView? { + return nil + } + func setupRenderLayer() -> DrawingRenderLayer? { return nil } @@ -334,6 +341,9 @@ final class FillTool: DrawingElement, Codable { if self.isBlur { if let blurredImage = self.blurredImage?.cgImage { + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) context.draw(blurredImage, in: CGRect(origin: .zero, size: size)) } } else { @@ -352,220 +362,3 @@ final class FillTool: DrawingElement, Codable { return false } } - - -final class BlurTool: DrawingElement, Codable { - class RenderLayer: SimpleLayer, DrawingRenderLayer { - var lineWidth: CGFloat = 0.0 - - let blurLayer = SimpleLayer() - let fillLayer = SimpleShapeLayer() - - func setup(size: CGSize, lineWidth: CGFloat, image: UIImage?) { - self.contentsScale = 1.0 - self.lineWidth = lineWidth - - let bounds = CGRect(origin: .zero, size: size) - self.frame = bounds - - self.blurLayer.frame = bounds - self.fillLayer.frame = bounds - - if self.blurLayer.contents == nil, let image = image { - self.blurLayer.contents = image.cgImage - } - self.blurLayer.mask = self.fillLayer - - self.fillLayer.frame = bounds - self.fillLayer.contentsScale = 1.0 - self.fillLayer.strokeColor = UIColor.white.cgColor - self.fillLayer.fillColor = UIColor.clear.cgColor - self.fillLayer.lineCap = .round - self.fillLayer.lineWidth = lineWidth - - self.addSublayer(self.blurLayer) - } - - func updatePath(_ path: CGPath) { - self.fillLayer.path = path - } - } - - var getFullImage: () -> UIImage? = { return nil } - - let uuid: UUID - - let drawingSize: CGSize - - var path: BezierPath? - - var renderPath: CGPath? - let renderLineWidth: CGFloat - - var translation = CGPoint() - - private var currentRenderLayer: DrawingRenderLayer? - - var isValid: Bool { - return self.renderPath != nil - } - - required init(drawingSize: CGSize, lineWidth: CGFloat) { - self.uuid = UUID() - self.drawingSize = drawingSize - - let minLineWidth = max(1.0, max(drawingSize.width, drawingSize.height) * 0.003) - let maxLineWidth = max(10.0, max(drawingSize.width, drawingSize.height) * 0.09) - let lineWidth = minLineWidth + (maxLineWidth - minLineWidth) * lineWidth - - 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, lineWidth: self.renderLineWidth, image: self.getFullImage()) - self.currentRenderLayer = layer - return layer - } - - func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) { - guard case let .smoothCurve(bezierPath) = path else { - return - } - - self.path = bezierPath - - let renderPath = bezierPath.path.cgPath - self.renderPath = renderPath - - if let currentRenderLayer = self.currentRenderLayer as? RenderLayer { - currentRenderLayer.updatePath(renderPath) - } - } - - func draw(in context: CGContext, size: CGSize) { - context.translateBy(x: self.translation.x, y: self.translation.y) - - let renderLayer: DrawingRenderLayer? - if let currentRenderLayer = self.currentRenderLayer { - renderLayer = currentRenderLayer - } else { - renderLayer = self.setupRenderLayer() - } - renderLayer?.render(in: context) - } -} - -enum CodableDrawingElement { - case pen(PenTool) - case marker(MarkerTool) - case neon(NeonTool) - 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? BlurTool { - self = .blur(element) - } else if let element = element as? FillTool { - self = .fill(element) - } else { - return nil - } - } - - var element: DrawingElement { - switch self { - case let .pen(element): - return element - case let .marker(element): - return element - case let .neon(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 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 .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 .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) - } - } -} diff --git a/submodules/DrawingUI/Sources/DrawingView.swift b/submodules/DrawingUI/Sources/DrawingView.swift index 5bf86f3eea..89ee88fc84 100644 --- a/submodules/DrawingUI/Sources/DrawingView.swift +++ b/submodules/DrawingUI/Sources/DrawingView.swift @@ -7,11 +7,20 @@ import LegacyComponents import AppBundle import ImageBlur +protocol DrawingRenderLayer: CALayer { + +} + +protocol DrawingRenderView: UIView { + +} + protocol DrawingElement: AnyObject { var uuid: UUID { get } var translation: CGPoint { get set } var isValid: Bool { get } + func setupRenderView(screenSize: CGSize) -> DrawingRenderView? func setupRenderLayer() -> DrawingRenderLayer? func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) @@ -68,7 +77,8 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw private(set) var drawingImage: UIImage? private let renderer: UIGraphicsImageRenderer - private var currentDrawingView: UIImageView + private var currentDrawingViewContainer: UIImageView + private var currentDrawingRenderView: DrawingRenderView? private var currentDrawingLayer: DrawingRenderLayer? private var pannedSelectionView: UIView @@ -123,11 +133,11 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw format.scale = 1.0 self.renderer = UIGraphicsImageRenderer(size: size, format: format) - self.currentDrawingView = UIImageView() - self.currentDrawingView.frame = CGRect(origin: .zero, size: size) - self.currentDrawingView.contentScaleFactor = 1.0 - self.currentDrawingView.backgroundColor = .clear - self.currentDrawingView.isUserInteractionEnabled = false + self.currentDrawingViewContainer = UIImageView() + self.currentDrawingViewContainer.frame = CGRect(origin: .zero, size: size) + self.currentDrawingViewContainer.contentScaleFactor = 1.0 + self.currentDrawingViewContainer.backgroundColor = .clear + self.currentDrawingViewContainer.isUserInteractionEnabled = false self.pannedSelectionView = UIView() self.pannedSelectionView.frame = CGRect(origin: .zero, size: size) @@ -159,8 +169,8 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.contentScaleFactor = 1.0 self.isExclusiveTouch = true - self.addSubview(self.currentDrawingView) - self.addSubview(self.metalView) + self.addSubview(self.currentDrawingViewContainer) + //self.addSubview(self.metalView) self.layer.addSublayer(self.brushSizePreviewLayer) @@ -203,24 +213,46 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw strongSelf.metalView.isHidden = false } + if let renderView = newElement.setupRenderView(screenSize: CGSize(width: 414.0, height: 414.0)) { + if let currentDrawingView = strongSelf.currentDrawingRenderView { + strongSelf.currentDrawingRenderView = nil + currentDrawingView.removeFromSuperview() + } + if strongSelf.tool == .eraser { + strongSelf.currentDrawingViewContainer.removeFromSuperview() + strongSelf.currentDrawingViewContainer.backgroundColor = .white + + renderView.layer.compositingFilter = "xor" + + strongSelf.currentDrawingViewContainer.addSubview(renderView) + strongSelf.mask = strongSelf.currentDrawingViewContainer + } else if strongSelf.tool == .blur { + strongSelf.currentDrawingViewContainer.mask = renderView + strongSelf.currentDrawingViewContainer.image = strongSelf.preparedBlurredImage + } else { + strongSelf.currentDrawingViewContainer.addSubview(renderView) + } + strongSelf.currentDrawingRenderView = renderView + } + if let renderLayer = newElement.setupRenderLayer() { if let currentDrawingLayer = strongSelf.currentDrawingLayer { strongSelf.currentDrawingLayer = nil currentDrawingLayer.removeFromSuperlayer() } if strongSelf.tool == .eraser { - strongSelf.currentDrawingView.removeFromSuperview() - strongSelf.currentDrawingView.backgroundColor = .white + strongSelf.currentDrawingViewContainer.removeFromSuperview() + strongSelf.currentDrawingViewContainer.backgroundColor = .white renderLayer.compositingFilter = "xor" - strongSelf.currentDrawingView.layer.addSublayer(renderLayer) - strongSelf.mask = strongSelf.currentDrawingView + strongSelf.currentDrawingViewContainer.layer.addSublayer(renderLayer) + strongSelf.mask = strongSelf.currentDrawingViewContainer } else if strongSelf.tool == .blur { - strongSelf.currentDrawingView.layer.mask = renderLayer - strongSelf.currentDrawingView.image = strongSelf.preparedBlurredImage + strongSelf.currentDrawingViewContainer.layer.mask = renderLayer + strongSelf.currentDrawingViewContainer.image = strongSelf.preparedBlurredImage } else { - strongSelf.currentDrawingView.layer.addSublayer(renderLayer) + strongSelf.currentDrawingViewContainer.layer.addSublayer(renderLayer) } strongSelf.currentDrawingLayer = renderLayer } @@ -351,13 +383,6 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw return nil } return self.drawingImage?.pngData() - -// 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 { @@ -388,8 +413,6 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw blurredImage = self.preparedBlurredImage } - self.hapticFeedback.tap() - let fillCircleLayer = SimpleShapeLayer() self.longPressTimer = SwiftSignalKit.Timer(timeout: 0.25, repeat: false, completion: { [weak self, weak fillCircleLayer] in if let strongSelf = self { @@ -482,15 +505,29 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.drawingImage = updatedImage self.layer.contents = updatedImage.cgImage + if let currentDrawingRenderView = self.currentDrawingRenderView { + if case .eraser = self.tool { + currentDrawingRenderView.removeFromSuperview() + self.mask = nil + self.insertSubview(self.currentDrawingViewContainer, at: 0) + self.currentDrawingViewContainer.backgroundColor = .clear + } else if case .blur = self.tool { + self.currentDrawingViewContainer.mask = nil + self.currentDrawingViewContainer.image = nil + } else { + currentDrawingRenderView.removeFromSuperview() + } + self.currentDrawingRenderView = nil + } if let currentDrawingLayer = self.currentDrawingLayer { if case .eraser = self.tool { currentDrawingLayer.removeFromSuperlayer() self.mask = nil - self.insertSubview(self.currentDrawingView, at: 0) - self.currentDrawingView.backgroundColor = .clear + self.insertSubview(self.currentDrawingViewContainer, at: 0) + self.currentDrawingViewContainer.backgroundColor = .clear } else if case .blur = self.tool { - self.currentDrawingView.layer.mask = nil - self.currentDrawingView.image = nil + self.currentDrawingViewContainer.layer.mask = nil + self.currentDrawingViewContainer.image = nil } else { currentDrawingLayer.removeFromSuperlayer() } @@ -527,14 +564,29 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw fileprivate func cancelDrawing() { self.uncommitedElement = nil + if let currentDrawingRenderView = self.currentDrawingRenderView { + if case .eraser = self.tool { + currentDrawingRenderView.removeFromSuperview() + self.mask = nil + self.insertSubview(self.currentDrawingViewContainer, at: 0) + self.currentDrawingViewContainer.backgroundColor = .clear + } else if case .blur = self.tool { + self.currentDrawingViewContainer.mask = nil + self.currentDrawingViewContainer.image = nil + } else { + currentDrawingRenderView.removeFromSuperview() + } + self.currentDrawingRenderView = nil + } if let currentDrawingLayer = self.currentDrawingLayer { if self.tool == .eraser { currentDrawingLayer.removeFromSuperlayer() self.mask = nil - self.insertSubview(self.currentDrawingView, at: 0) - self.currentDrawingView.backgroundColor = .clear + self.insertSubview(self.currentDrawingViewContainer, at: 0) + self.currentDrawingViewContainer.backgroundColor = .clear } else if self.tool == .blur { - self.currentDrawingView.mask = nil + self.currentDrawingViewContainer.mask = nil + self.currentDrawingViewContainer.image = nil } else { currentDrawingLayer.removeFromSuperlayer() } @@ -723,10 +775,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw if self.tool != previousTool { self.updateBlurredImage() - } else { - self.preparedBlurredImage = nil } - } func updateBlurredImage() { @@ -870,8 +919,8 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw let scale = self.scale let transform = CGAffineTransformMakeScale(scale, scale) - self.currentDrawingView.transform = transform - self.currentDrawingView.frame = self.bounds + self.currentDrawingViewContainer.transform = transform + self.currentDrawingViewContainer.frame = self.bounds self.drawingGesturePipeline?.transform = CGAffineTransformMakeScale(1.0 / scale, 1.0 / scale) @@ -882,7 +931,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw } public var isEmpty: Bool { - return self.elements.isEmpty + return self.elements.isEmpty && !self.hasOpaqueData } public var scale: CGFloat { @@ -893,3 +942,22 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw return self.uncommitedElement != nil } } + +private class UndoSlice { + let uuid: UUID + +// let data: Data +// let bounds: CGRect + + let path: String + + init(context: DrawingContext, bounds: CGRect) { + self.uuid = UUID() + + self.path = NSTemporaryDirectory() + "/drawing_\(uuid.hashValue).slice" + } + + deinit { + try? FileManager.default.removeItem(atPath: self.path) + } +} diff --git a/submodules/DrawingUI/Sources/PenTool.swift b/submodules/DrawingUI/Sources/PenTool.swift index 10b323fc1b..ae4e1f2477 100644 --- a/submodules/DrawingUI/Sources/PenTool.swift +++ b/submodules/DrawingUI/Sources/PenTool.swift @@ -3,70 +3,68 @@ import UIKit import Display final class PenTool: DrawingElement, Codable { - class RenderLayer: SimpleLayer, DrawingRenderLayer { + class RenderView: UIView, DrawingRenderView { private weak var element: PenTool? private var isEraser = false private var accumulationImage: UIImage? - private var activeLayer: ActiveLayer? + private var activeView: ActiveView? private var start = 0 private var segmentsCount = 0 private var velocity: CGFloat? - var displayLink: ConstantDisplayLinkAnimator? - func setup(size: CGSize, isEraser: Bool, useDisplayLink: Bool) { + private var displayScale: CGFloat = 1.0 + + func setup(size: CGSize, screenSize: CGSize, isEraser: Bool, useDisplayLink: Bool) { self.isEraser = isEraser - self.shouldRasterize = true - self.contentsScale = 1.0 - self.allowsGroupOpacity = true + self.backgroundColor = .clear + self.isOpaque = false + self.contentMode = .redraw - let bounds = CGRect(origin: .zero, size: size) - self.frame = bounds + let viewSize = size.aspectFilled(screenSize) - let activeLayer = ActiveLayer() - activeLayer.shouldRasterize = true - activeLayer.contentsScale = 1.0 - activeLayer.frame = bounds - activeLayer.parent = self - self.addSublayer(activeLayer) - self.activeLayer = activeLayer + self.displayScale = size.width / viewSize.width - if useDisplayLink { - self.displayLink = ConstantDisplayLinkAnimator(update: { [weak self] in - if let strongSelf = self { - if let element = strongSelf.element, strongSelf.segmentsCount < element.segments.count, let velocity = strongSelf.velocity { - let delta = max(12, Int(velocity / 100.0)) - let start = strongSelf.segmentsCount - strongSelf.segmentsCount = min(strongSelf.segmentsCount + delta, element.segments.count) - - let rect = element.boundingRect(from: start, to: strongSelf.segmentsCount) - strongSelf.activeLayer?.setNeedsDisplay(rect.insetBy(dx: -10.0, dy: -10.0)) - } - } - }) - self.displayLink?.frameInterval = 1 - self.displayLink?.isPaused = false - } + self.bounds = CGRect(origin: .zero, size: viewSize) + self.transform = CGAffineTransform(scaleX: self.displayScale, y: self.displayScale) + self.frame = CGRect(origin: .zero, size: size) + + let activeView = ActiveView(frame: CGRect(origin: .zero, size: viewSize)) + activeView.backgroundColor = .clear + activeView.contentMode = .redraw + activeView.isOpaque = false + activeView.parent = self + self.addSubview(activeView) + self.activeView = activeView } - func animateArrowPaths(leftArrowPath: UIBezierPath, rightArrowPath: UIBezierPath, lineWidth: CGFloat, completion: @escaping () -> Void) { + func animateArrowPaths(start: CGPoint, direction: CGFloat, length: CGFloat, lineWidth: CGFloat, completion: @escaping () -> Void) { + let arrowStart = CGPoint(x: start.x / self.displayScale, y: start.y / self.displayScale) + let arrowLeftPath = UIBezierPath() + arrowLeftPath.move(to: arrowStart) + arrowLeftPath.addLine(to: arrowStart.pointAt(distance: length / self.displayScale, angle: direction - 0.45)) + + let arrowRightPath = UIBezierPath() + arrowRightPath.move(to: arrowStart) + arrowRightPath.addLine(to: arrowStart.pointAt(distance: length / self.displayScale, angle: direction + 0.45)) + let leftArrowShape = CAShapeLayer() - leftArrowShape.path = leftArrowPath.cgPath - leftArrowShape.lineWidth = lineWidth + leftArrowShape.path = arrowLeftPath.cgPath + leftArrowShape.lineWidth = lineWidth / self.displayScale leftArrowShape.strokeColor = self.element?.color.toCGColor() leftArrowShape.lineCap = .round leftArrowShape.frame = self.bounds - self.addSublayer(leftArrowShape) + self.layer.addSublayer(leftArrowShape) let rightArrowShape = CAShapeLayer() - rightArrowShape.path = rightArrowPath.cgPath - rightArrowShape.lineWidth = lineWidth + rightArrowShape.path = arrowRightPath.cgPath + rightArrowShape.lineWidth = lineWidth / self.displayScale rightArrowShape.strokeColor = self.element?.color.toCGColor() rightArrowShape.lineCap = .round rightArrowShape.frame = self.bounds - self.addSublayer(rightArrowShape) + self.layer.addSublayer(rightArrowShape) leftArrowShape.animate(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "strokeEnd", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2) rightArrowShape.animate(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "strokeEnd", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, completion: { [weak leftArrowShape, weak rightArrowShape] _ in @@ -76,12 +74,11 @@ final class PenTool: DrawingElement, Codable { rightArrowShape?.removeFromSuperlayer() }) } - - var i = 0 + fileprivate func draw(element: PenTool, velocity: CGFloat, rect: CGRect) { self.element = element - self.opacity = Float(element.color.alpha) + self.alpha = element.color.alpha guard !rect.isInfinite && !rect.isEmpty && !rect.isNull else { return @@ -95,48 +92,53 @@ final class PenTool: DrawingElement, Codable { rect = nil let newStart = self.start + limit let image = generateImage(self.bounds.size, contextGenerator: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + if let accumulationImage = self.accumulationImage, let cgImage = accumulationImage.cgImage { context.draw(cgImage, in: CGRect(origin: .zero, size: size)) } + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) context.scaleBy(x: 1.0, y: -1.0) context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + + context.scaleBy(x: 1.0 / self.displayScale, y: 1.0 / self.displayScale) + + context.setBlendMode(.copy) element.drawSegments(in: context, from: self.start, upTo: newStart) - }, opaque: false, scale: 1.0) + }, opaque: false) self.accumulationImage = image - self.contents = image?.cgImage - i += 1 - print("accumulated \(i)") + self.layer.contents = image?.cgImage self.start = newStart } self.segmentsCount = element.segments.count - if let previous = self.velocity { - self.velocity = velocity * 0.4 + previous * 0.6 - } else { - self.velocity = velocity - } +// if let previous = self.velocity { +// self.velocity = velocity * 0.4 + previous * 0.6 +// } else { +// self.velocity = velocity +// } if let rect = rect { - self.activeLayer?.setNeedsDisplay(rect.insetBy(dx: -10.0, dy: -10.0)) + self.activeView?.setNeedsDisplay(rect.insetBy(dx: -10.0, dy: -10.0).applying(CGAffineTransform(scaleX: 1.0 / self.displayScale, y: 1.0 / self.displayScale))) } else { - self.activeLayer?.setNeedsDisplay() + self.activeView?.setNeedsDisplay() } } - class ActiveLayer: SimpleLayer { - weak var parent: RenderLayer? - - override func draw(in ctx: CGContext) { - guard let parent = self.parent, let element = parent.element else { + class ActiveView: UIView { + weak var parent: RenderView? + override func draw(_ rect: CGRect) { + guard let context = UIGraphicsGetCurrentContext(), let parent = self.parent, let element = parent.element else { return } - element.drawSegments(in: ctx, from: parent.start, upTo: parent.segmentsCount) + context.scaleBy(x: 1.0 / parent.displayScale, y: 1.0 / parent.displayScale) + element.drawSegments(in: context, from: parent.start, upTo: parent.segmentsCount) } } } - + let uuid: UUID let drawingSize: CGSize let color: DrawingColor @@ -161,7 +163,7 @@ final class PenTool: DrawingElement, Codable { var blurredImage: UIImage? - private weak var currentRenderLayer: DrawingRenderLayer? + private weak var currentRenderView: DrawingRenderView? var isValid: Bool { if self.hasArrow { @@ -256,9 +258,9 @@ final class PenTool: DrawingElement, Codable { var isFinishingArrow = false func finishArrow(_ completion: @escaping () -> Void) { - if let arrowLeftPath, let arrowRightPath { + if let arrowStart, let arrowDirection { self.isFinishingArrow = true - (self.currentRenderLayer as? RenderLayer)?.animateArrowPaths(leftArrowPath: arrowLeftPath, rightArrowPath: arrowRightPath, lineWidth: self.renderArrowLineWidth, completion: { [weak self] in + (self.currentRenderView as? RenderView)?.animateArrowPaths(start: arrowStart, direction: arrowDirection, length: self.renderArrowLength, lineWidth: self.renderArrowLineWidth, completion: { [weak self] in self?.isFinishingArrow = false completion() }) @@ -267,11 +269,15 @@ final class PenTool: DrawingElement, Codable { } } + func setupRenderView(screenSize: CGSize) -> DrawingRenderView? { + let view = RenderView() + view.setup(size: self.drawingSize, screenSize: screenSize, isEraser: self.isEraser, useDisplayLink: self.color.toUIColor().rgb == 0xbf5af2) + self.currentRenderView = view + return view + } + func setupRenderLayer() -> DrawingRenderLayer? { - let layer = RenderLayer() - layer.setup(size: self.drawingSize, isEraser: self.isEraser, useDisplayLink: self.color.toUIColor().rgb == 0xbf5af2) - self.currentRenderLayer = layer - return layer + return nil } var previousPoint: CGPoint? @@ -282,11 +288,10 @@ final class PenTool: DrawingElement, Codable { var filterDistance: CGFloat if point.velocity > 1200.0 { - filterDistance = 90.0 + filterDistance = 70.0 } else { - filterDistance = 20.0 + filterDistance = 15.0 } -// filterDistance = 0.0 if let previousPoint, point.location.distance(to: previousPoint) < filterDistance, state == .changed, self.segments.count > 1 { return @@ -306,8 +311,8 @@ final class PenTool: DrawingElement, Codable { 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 let currentRenderView = self.currentRenderView as? RenderView, let rect = rect { + currentRenderView.draw(element: self, velocity: point.velocity, rect: rect) } if state == .ended { diff --git a/submodules/DrawingUI/Sources/TextSettingsComponent.swift b/submodules/DrawingUI/Sources/TextSettingsComponent.swift index a5b48f6263..85cc1a54a0 100644 --- a/submodules/DrawingUI/Sources/TextSettingsComponent.swift +++ b/submodules/DrawingUI/Sources/TextSettingsComponent.swift @@ -760,6 +760,7 @@ final class TextSizeSliderComponent: Component { init() { super.init(frame: CGRect()) + self.layer.allowsGroupOpacity = true self.isExclusiveTouch = true let pressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handlePress(_:))) diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h index ae7176fdfb..3e21657bdc 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h @@ -77,9 +77,9 @@ typedef enum @property (nonatomic, strong) NSString *recipientName; @property (nonatomic, copy) NSDictionary *(^descriptionGenerator)(id, NSAttributedString *, NSString *, NSString *); -@property (nonatomic, copy) void (^avatarCompletionBlock)(UIImage *image); +@property (nonatomic, copy) void (^avatarCompletionBlock)(UIImage *image, void(^commit)(void)); @property (nonatomic, copy) void (^completionBlock)(NSArray *signals, bool silentPosting, int32_t scheduleTime); -@property (nonatomic, copy) void (^avatarVideoCompletionBlock)(UIImage *image, AVAsset *asset, TGVideoEditAdjustments *adjustments); +@property (nonatomic, copy) void (^avatarVideoCompletionBlock)(UIImage *image, AVAsset *asset, TGVideoEditAdjustments *adjustments, void(^commit)(void)); @property (nonatomic, copy) void (^singleCompletionBlock)(id item, TGMediaEditingContext *editingContext); @property (nonatomic, copy) void (^dismissalBlock)(void); @property (nonatomic, copy) void (^selectionBlock)(TGMediaAsset *asset, UIImage *); @@ -100,8 +100,8 @@ typedef enum - (NSArray *)resultSignalsWithCurrentItem:(TGMediaAsset *)currentItem descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *, NSString *))descriptionGenerator; -- (void)completeWithAvatarImage:(UIImage *)image; -- (void)completeWithAvatarVideo:(id)asset adjustments:(TGVideoEditAdjustments *)adjustments image:(UIImage *)image; +- (void)completeWithAvatarImage:(UIImage *)image commit:(void(^)(void))commit; +- (void)completeWithAvatarVideo:(id)asset adjustments:(TGVideoEditAdjustments *)adjustments image:(UIImage *)image commit:(void(^)(void))commit; - (void)completeWithCurrentItem:(TGMediaAsset *)currentItem silentPosting:(bool)silentPosting scheduleTime:(int32_t)scheduleTime; - (void)dismiss; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 83134f349d..74f4a95599 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -816,16 +816,16 @@ #pragma mark - -- (void)completeWithAvatarImage:(UIImage *)image +- (void)completeWithAvatarImage:(UIImage *)image commit:(void(^)(void))commit { if (self.avatarCompletionBlock != nil) - self.avatarCompletionBlock(image); + self.avatarCompletionBlock(image, commit); } -- (void)completeWithAvatarVideo:(AVAsset *)asset adjustments:(TGVideoEditAdjustments *)adjustments image:(UIImage *)image +- (void)completeWithAvatarVideo:(AVAsset *)asset adjustments:(TGVideoEditAdjustments *)adjustments image:(UIImage *)image commit:(void(^)(void))commit { if (self.avatarVideoCompletionBlock != nil) - self.avatarVideoCompletionBlock(image, asset, adjustments); + self.avatarVideoCompletionBlock(image, asset, adjustments, commit); } - (void)completeWithCurrentItem:(TGMediaAsset *)currentItem silentPosting:(bool)silentPosting scheduleTime:(int32_t)scheduleTime diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m index d57defabf2..d6d9d2420b 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m @@ -491,12 +491,10 @@ previewImage = thumbnailImage; } } - [(TGMediaAssetsController *)strongSelf.navigationController completeWithAvatarVideo:[NSURL fileURLWithPath:filePath] adjustments:videoAdjustments image:previewImage]; + [(TGMediaAssetsController *)strongSelf.navigationController completeWithAvatarVideo:[NSURL fileURLWithPath:filePath] adjustments:videoAdjustments image:previewImage commit:commit]; } else { - [(TGMediaAssetsController *)strongSelf.navigationController completeWithAvatarImage:resultImage]; + [(TGMediaAssetsController *)strongSelf.navigationController completeWithAvatarImage:resultImage commit:commit]; } - - commit(); }; controller.didFinishEditingVideo = ^(AVAsset *asset, id adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges, void(^commit)(void)) { if (!hasChanges) @@ -506,7 +504,7 @@ if (strongSelf == nil) return; - [(TGMediaAssetsController *)strongSelf.navigationController completeWithAvatarVideo:asset adjustments:adjustments image:resultImage]; + [(TGMediaAssetsController *)strongSelf.navigationController completeWithAvatarVideo:asset adjustments:adjustments image:resultImage commit:commit]; }; controller.requestThumbnailImage = ^(id editableItem) { diff --git a/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m b/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m index a91456c19c..ee5bb62838 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m +++ b/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m @@ -169,8 +169,7 @@ }); } else { if (strongSelf.didFinishWithVideo != nil) - strongSelf.didFinishWithVideo(image, - asset, adjustments); + strongSelf.didFinishWithVideo(image, asset, adjustments); commit(); @@ -473,17 +472,18 @@ controller.stickersContext = _stickersContext; controller.forum = _forum; controller.isSuggesting = _isSuggesting; - controller.avatarCompletionBlock = ^(UIImage *resultImage) - { + controller.avatarCompletionBlock = ^(UIImage *resultImage, void(^commit)(void)) { __strong TGMediaAvatarMenuMixin *strongSelf = weakSelf; if (strongSelf == nil) return; - + if (strongSelf.willFinishWithImage != nil) { strongSelf.willFinishWithImage(resultImage, ^{ if (strongSelf.didFinishWithImage != nil) strongSelf.didFinishWithImage(resultImage); + commit(); + __strong TGMediaAssetsController *strongController = weakController; if (strongController != nil && strongController.dismissalBlock != nil) strongController.dismissalBlock(); @@ -492,22 +492,40 @@ if (strongSelf.didFinishWithImage != nil) strongSelf.didFinishWithImage(resultImage); + commit(); + __strong TGMediaAssetsController *strongController = weakController; if (strongController != nil && strongController.dismissalBlock != nil) strongController.dismissalBlock(); } }; - controller.avatarVideoCompletionBlock = ^(UIImage *image, AVAsset *asset, TGVideoEditAdjustments *adjustments) { + controller.avatarVideoCompletionBlock = ^(UIImage *image, AVAsset *asset, TGVideoEditAdjustments *adjustments, void(^commit)(void)) { __strong TGMediaAvatarMenuMixin *strongSelf = weakSelf; if (strongSelf == nil) return; - if (strongSelf.didFinishWithVideo != nil) - strongSelf.didFinishWithVideo(image, asset, adjustments); - __strong TGMediaAssetsController *strongController = weakController; - if (strongController != nil && strongController.dismissalBlock != nil) - strongController.dismissalBlock(); + if (strongSelf.willFinishWithVideo != nil) { + strongSelf.willFinishWithVideo(image, ^{ + if (strongSelf.didFinishWithVideo != nil) + strongSelf.didFinishWithVideo(image, asset, adjustments); + + commit(); + + __strong TGMediaAssetsController *strongController = weakController; + if (strongController != nil && strongController.dismissalBlock != nil) + strongController.dismissalBlock(); + }); + } else { + if (strongSelf.didFinishWithVideo != nil) + strongSelf.didFinishWithVideo(image, asset, adjustments); + + commit(); + + __strong TGMediaAssetsController *strongController = weakController; + if (strongController != nil && strongController.dismissalBlock != nil) + strongController.dismissalBlock(); + } }; return presentBlock(controller); };