diff --git a/submodules/Camera/BUILD b/submodules/Camera/BUILD index 5be33845cc..eb09de4aa3 100644 --- a/submodules/Camera/BUILD +++ b/submodules/Camera/BUILD @@ -56,6 +56,7 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", + "//submodules/ImageBlur:ImageBlur", ], visibility = [ "//visibility:public", diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index a205b8221c..2a2daea5b1 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -1,6 +1,8 @@ import Foundation +import UIKit import SwiftSignalKit import AVFoundation +import CoreImage private final class CameraContext { private let queue: Queue @@ -38,6 +40,19 @@ private final class CameraContext { } } + private var lastSnapshotTimestamp: Double = CACurrentMediaTime() + private func savePreviewSnapshot(pixelBuffer: CVPixelBuffer) { + Queue.concurrentDefaultQueue().async { + let ciContext = CIContext() + var ciImage = CIImage(cvImageBuffer: pixelBuffer) + ciImage = ciImage.transformed(by: CGAffineTransform(scaleX: 0.33, y: 0.33)) + if let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) { + let uiImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .right) + CameraSimplePreviewView.saveLastState(uiImage) + } + } + } + private var videoOrientation: AVCaptureVideoOrientation? init(queue: Queue, session: AVCaptureSession, configuration: Camera.Configuration, metrics: Camera.Metrics, previewView: CameraSimplePreviewView?) { self.queue = queue @@ -59,24 +74,30 @@ private final class CameraContext { guard let self else { return } - if let previewView = self.previewView, !self.changingPosition { - let videoOrientation = connection.videoOrientation - if #available(iOS 13.0, *) { - previewView.mirroring = connection.inputPorts.first?.sourceDevicePosition == .front - } - if let rotation = CameraPreviewView.Rotation(with: .portrait, videoOrientation: videoOrientation, cameraPosition: self.device.position) { - previewView.rotation = rotation - } - if #available(iOS 13.0, *), connection.inputPorts.first?.sourceDevicePosition == .front { - let width = CVPixelBufferGetWidth(pixelBuffer) - let height = CVPixelBufferGetHeight(pixelBuffer) - previewView.captureDeviceResolution = CGSize(width: width, height: height) - } - previewView.pixelBuffer = pixelBuffer - Queue.mainQueue().async { - self.videoOrientation = videoOrientation - } + + let timestamp = CACurrentMediaTime() + if timestamp > self.lastSnapshotTimestamp + 5.0 { + self.savePreviewSnapshot(pixelBuffer: pixelBuffer) + self.lastSnapshotTimestamp = timestamp } +// if let previewView = self.previewView, !self.changingPosition { +// let videoOrientation = connection.videoOrientation +// if #available(iOS 13.0, *) { +// previewView.mirroring = connection.inputPorts.first?.sourceDevicePosition == .front +// } +// if let rotation = CameraPreviewView.Rotation(with: .portrait, videoOrientation: videoOrientation, cameraPosition: self.device.position) { +// previewView.rotation = rotation +// } +// if #available(iOS 13.0, *), connection.inputPorts.first?.sourceDevicePosition == .front { +// let width = CVPixelBufferGetWidth(pixelBuffer) +// let height = CVPixelBufferGetHeight(pixelBuffer) +// previewView.captureDeviceResolution = CGSize(width: width, height: height) +// } +// previewView.pixelBuffer = pixelBuffer +// Queue.mainQueue().async { +// self.videoOrientation = videoOrientation +// } +// } } self.output.processFaceLandmarks = { [weak self] observations in diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index 1933b559ed..5735944198 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -225,6 +225,10 @@ extension CameraOutput: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureA } } + if let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { + self.processSampleBuffer?(videoPixelBuffer, connection) + } + // let finalSampleBuffer: CMSampleBuffer = sampleBuffer // if let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer), let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer) { // var finalVideoPixelBuffer = videoPixelBuffer diff --git a/submodules/Camera/Sources/CameraPreviewView.swift b/submodules/Camera/Sources/CameraPreviewView.swift index 0c326a8c64..974f6668c4 100644 --- a/submodules/Camera/Sources/CameraPreviewView.swift +++ b/submodules/Camera/Sources/CameraPreviewView.swift @@ -7,8 +7,65 @@ import Metal import MetalKit import CoreMedia import Vision +import ImageBlur public class CameraSimplePreviewView: UIView { + static func lastStateImage() -> UIImage { + let imagePath = NSTemporaryDirectory() + "cameraImage.jpg" + if let data = try? Data(contentsOf: URL(fileURLWithPath: imagePath)), let image = UIImage(data: data) { + return image + } else { + return UIImage(bundleImageName: "Camera/Placeholder")! + } + } + + static func saveLastState(_ image: UIImage) { + let imagePath = NSTemporaryDirectory() + "cameraImage.jpg" + if let blurredImage = blurredImage(image, radius: 60.0), let data = blurredImage.jpegData(compressionQuality: 0.85) { + try? data.write(to: URL(fileURLWithPath: imagePath)) + } + } + + private var previewingDisposable: Disposable? + private let placeholderView = UIImageView() + public override init(frame: CGRect) { + super.init(frame: frame) + + self.placeholderView.image = CameraSimplePreviewView.lastStateImage() + self.addSubview(self.placeholderView) + + if #available(iOS 13.0, *) { + self.previewingDisposable = (self.isPreviewing + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] _ in + UIView.animate(withDuration: 0.3) { + self?.placeholderView.alpha = 0.0 + } + }) + } else { + Queue.mainQueue().after(0.5) { + UIView.animate(withDuration: 0.3) { + self.placeholderView.alpha = 0.0 + } + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.previewingDisposable?.dispose() + } + + public override func layoutSubviews() { + super.layoutSubviews() + + self.placeholderView.frame = self.bounds + } + var videoPreviewLayer: AVCaptureVideoPreviewLayer { guard let layer = layer as? AVCaptureVideoPreviewLayer else { fatalError() diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushSelectedPen.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushSelectedPen.imageset/Contents.json new file mode 100644 index 0000000000..0402056ad7 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushSelectedPen.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_brush1.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushSelectedPen.imageset/ic_editor_brush1.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushSelectedPen.imageset/ic_editor_brush1.pdf new file mode 100644 index 0000000000..cafe10024a Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushSelectedPen.imageset/ic_editor_brush1.pdf differ diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index 73f927be70..907c47263c 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -254,10 +254,6 @@ private final class CameraScreenComponent: CombinedComponent { let controller = environment.controller let availableSize = context.availableSize - let accountContext = component.context - let push = component.push - let completion = component.completion - let topControlInset: CGFloat = 20.0 component.changeMode.connect({ [weak state] mode in @@ -394,15 +390,10 @@ private final class CameraScreenComponent: CombinedComponent { state.camera.togglePosition() }, galleryTapped: { - var dismissGalleryControllerImpl: (() -> Void)? - let controller = accountContext.sharedContext.makeMediaPickerScreen(context: accountContext, completion: { asset in - dismissGalleryControllerImpl?() - completion.invoke(.single(.asset(asset))) - }) - dismissGalleryControllerImpl = { [weak controller] in - controller?.dismiss(animated: true) + guard let controller = environment.controller() as? CameraScreen else { + return } - push(controller) + controller.presentGallery() }, swipeHintUpdated: { hint in state.updateSwipeHint(hint) @@ -824,6 +815,10 @@ public class CameraScreen: ViewController { self.containerLayoutUpdated(layout: layout, transition: .easeInOut(duration: 0.2)) } } + } else if translation.y < -10.0 { + self.controller?.presentGallery() + gestureRecognizer.isEnabled = false + gestureRecognizer.isEnabled = true } } case .ended: @@ -872,12 +867,13 @@ public class CameraScreen: ViewController { if let transitionIn = self.controller?.transitionIn, let sourceView = transitionIn.sourceView { let sourceLocalFrame = sourceView.convert(transitionIn.sourceRect, to: self.view) - let innerSourceLocalFrame = CGRect(origin: CGPoint(x: sourceLocalFrame.minX - self.previewContainerView.frame.minX, y: sourceLocalFrame.minY - self.previewContainerView.frame.minY), size: sourceLocalFrame.size) - + + let sourceScale = sourceLocalFrame.width / self.previewContainerView.frame.width self.previewContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: self.previewContainerView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - self.previewContainerView.layer.animateBounds(from: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), to: self.previewContainerView.bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + self.previewContainerView.layer.animateScale(from: sourceScale, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + self.previewContainerView.layer.animateBounds(from: CGRect(origin: CGPoint(x: 0.0, y: (self.previewContainerView.bounds.height - self.previewContainerView.bounds.width) / 2.0), size: CGSize(width: self.previewContainerView.bounds.width, height: self.previewContainerView.bounds.width)), to: self.previewContainerView.bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) self.previewContainerView.layer.animate( - from: transitionIn.sourceCornerRadius as NSNumber, + from: self.previewContainerView.bounds.width / 2.0 as NSNumber, to: self.previewContainerView.layer.cornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: kCAMediaTimingFunctionSpring, @@ -886,7 +882,7 @@ public class CameraScreen: ViewController { if let view = self.componentHost.view { view.layer.animatePosition(from: sourceLocalFrame.center, to: view.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - view.layer.animateBounds(from: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), to: view.bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) } } } @@ -902,16 +898,17 @@ public class CameraScreen: ViewController { }) if let transitionOut = self.controller?.transitionOut(false), let destinationView = transitionOut.destinationView { - let sourceLocalFrame = destinationView.convert(transitionOut.destinationRect, to: self.view) - let innerSourceLocalFrame = CGRect(origin: CGPoint(x: sourceLocalFrame.minX - self.previewContainerView.frame.minX, y: sourceLocalFrame.minY - self.previewContainerView.frame.minY), size: sourceLocalFrame.size) + let destinationLocalFrame = destinationView.convert(transitionOut.destinationRect, to: self.view) - self.previewContainerView.layer.animatePosition(from: self.previewContainerView.center, to: sourceLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + let targetScale = destinationLocalFrame.width / self.previewContainerView.frame.width + self.previewContainerView.layer.animatePosition(from: self.previewContainerView.center, to: destinationLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in completion() }) - self.previewContainerView.layer.animateBounds(from: self.previewContainerView.bounds, to: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + self.previewContainerView.layer.animateScale(from: 1.0, to: targetScale, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + self.previewContainerView.layer.animateBounds(from: self.previewContainerView.bounds, to: CGRect(origin: CGPoint(x: 0.0, y: (self.previewContainerView.bounds.height - self.previewContainerView.bounds.width) / 2.0), size: CGSize(width: self.previewContainerView.bounds.width, height: self.previewContainerView.bounds.width)), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) self.previewContainerView.layer.animate( from: self.previewContainerView.layer.cornerRadius as NSNumber, - to: transitionOut.destinationCornerRadius as NSNumber, + to: self.previewContainerView.bounds.width / 2.0 as NSNumber, keyPath: "cornerRadius", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.3, @@ -919,8 +916,8 @@ public class CameraScreen: ViewController { ) if let view = self.componentHost.view { - view.layer.animatePosition(from: view.center, to: sourceLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - view.layer.animateBounds(from: view.bounds, to: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + view.layer.animatePosition(from: view.center, to: destinationLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + view.layer.animateScale(from: 1.0, to: targetScale, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) } } @@ -1075,10 +1072,6 @@ public class CameraScreen: ViewController { let componentFrame = CGRect(origin: .zero, size: componentSize) transition.setFrame(view: componentView, frame: componentFrame) } - - if isFirstTime { - self.animateIn() - } } transition.setFrame(view: self.backgroundDimView, frame: CGRect(origin: .zero, size: layout.size)) @@ -1090,6 +1083,10 @@ public class CameraScreen: ViewController { transition.setFrame(view: self.effectivePreviewView, frame: CGRect(origin: .zero, size: previewFrame.size)) transition.setFrame(view: self.previewBlurView, frame: CGRect(origin: .zero, size: previewFrame.size)) } + + if isFirstTime { + self.animateIn() + } } } @@ -1140,6 +1137,20 @@ public class CameraScreen: ViewController { public func returnFromEditor() { self.node.animateInFromEditor() } + + func presentGallery() { + var dismissGalleryControllerImpl: (() -> Void)? + let controller = self.context.sharedContext.makeMediaPickerScreen(context: self.context, completion: { [weak self] asset in + dismissGalleryControllerImpl?() + if let self { + self.completion(.single(.asset(asset))) + } + }) + dismissGalleryControllerImpl = { [weak controller] in + controller?.dismiss(animated: true) + } + push(controller) + } private var isDismissed = false fileprivate func requestDismiss(animated: Bool) { diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift index 7269c60910..8acf2cf30d 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift @@ -364,6 +364,8 @@ final class CaptureControlsComponent: Component { private let lockImage = UIImage(bundleImageName: "Camera/LockIcon") + private let hapticFeedback = HapticFeedback() + public func matches(tag: Any) -> Bool { if let component = self.component, let componentTag = component.tag { let tag = tag as AnyObject @@ -392,7 +394,6 @@ final class CaptureControlsComponent: Component { let location = gestureRecognizer.location(in: self) switch gestureRecognizer.state { case .began: - self.hapticFeedback.impact(.click05) self.component?.shutterPressed() self.component?.swipeHintUpdated(.zoom) self.shutterUpdateOffset.invoke((0.0, .immediate)) @@ -415,8 +416,6 @@ final class CaptureControlsComponent: Component { } } - private let hapticFeedback = HapticFeedback() - private var didFlip = false private var wasBanding: Bool? private var panBlobState: ShutterBlobView.BlobState? @@ -667,6 +666,7 @@ final class CaptureControlsComponent: Component { ), automaticHighlight: false, action: { [weak self] in + self?.hapticFeedback.impact(.light) self?.shutterUpdateOffset.invoke((0.0, .immediate)) component.shutterTapped() }, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 3d0b72ce37..f037d3e333 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -988,7 +988,7 @@ public final class MediaEditorScreen: ViewController { completion() }) self.previewContainerView.layer.animateScale(from: 1.0, to: targetScale, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - self.previewContainerView.layer.animateBounds(from: self.previewContainerView.bounds, to: CGRect(origin: CGPoint(x: 0.0, y: (self.previewContainerView.frame.height - self.previewContainerView.frame.width) / 2.0), size: CGSize(width: self.previewContainerView.bounds.width, height: self.previewContainerView.bounds.width)), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + self.previewContainerView.layer.animateBounds(from: self.previewContainerView.bounds, to: CGRect(origin: CGPoint(x: 0.0, y: (self.previewContainerView.bounds.height - self.previewContainerView.bounds.width) / 2.0), size: CGSize(width: self.previewContainerView.bounds.width, height: self.previewContainerView.bounds.width)), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) self.previewContainerView.layer.animate( from: self.previewContainerView.layer.cornerRadius as NSNumber, to: self.previewContainerView.bounds.width / 2.0 as NSNumber, @@ -1002,7 +1002,7 @@ public final class MediaEditorScreen: ViewController { componentView.clipsToBounds = true componentView.layer.animatePosition(from: componentView.center, to: destinationLocalFrame.center, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) componentView.layer.animateScale(from: 1.0, to: targetScale, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - componentView.layer.animateBounds(from: componentView.bounds, to: CGRect(origin: CGPoint(x: 0.0, y: (componentView.frame.height - componentView.frame.width) / 2.0), size: CGSize(width: componentView.bounds.width, height: componentView.bounds.width)), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + componentView.layer.animateBounds(from: componentView.bounds, to: CGRect(origin: CGPoint(x: 0.0, y: (componentView.bounds.height - componentView.bounds.width) / 2.0), size: CGSize(width: componentView.bounds.width, height: componentView.bounds.width)), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) componentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) componentView.layer.animate( from: componentView.layer.cornerRadius as NSNumber, diff --git a/submodules/TelegramUI/Images.xcassets/Camera/Placeholder.imageset/CameraPlaceholder.jpg b/submodules/TelegramUI/Images.xcassets/Camera/Placeholder.imageset/CameraPlaceholder.jpg new file mode 100644 index 0000000000..d7d326fa0e Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Camera/Placeholder.imageset/CameraPlaceholder.jpg differ diff --git a/submodules/TelegramUI/Images.xcassets/Camera/Placeholder.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Camera/Placeholder.imageset/Contents.json new file mode 100644 index 0000000000..e3cf874869 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Camera/Placeholder.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "CameraPlaceholder.jpg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +}