Camera and editor improvements

This commit is contained in:
Ilya Laktyushin 2023-05-16 17:08:55 +04:00
parent 65ed79b44d
commit 97e871fa22
11 changed files with 168 additions and 50 deletions

View File

@ -56,6 +56,7 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/ImageBlur:ImageBlur",
],
visibility = [
"//visibility:public",

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_editor_brush1.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -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) {

View File

@ -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()
},

View File

@ -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,

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "CameraPlaceholder.jpg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}