Camera and editor improvements

This commit is contained in:
Ilya Laktyushin 2023-06-03 11:51:33 +04:00
parent 3c39e2c0ce
commit ae32ccca68
5 changed files with 101 additions and 45 deletions

View File

@ -223,6 +223,10 @@ private final class CameraContext {
self.device.setZoomLevel(zoomLevel) self.device.setZoomLevel(zoomLevel)
} }
func setZoomDelta(_ zoomDelta: CGFloat) {
self.device.setZoomDelta(zoomDelta)
}
func takePhoto() -> Signal<PhotoCaptureResult, NoError> { func takePhoto() -> Signal<PhotoCaptureResult, NoError> {
return self.output.takePhoto(orientation: self.videoOrientation ?? .portrait, flashMode: self._flashMode) return self.output.takePhoto(orientation: self.videoOrientation ?? .portrait, flashMode: self._flashMode)
} }
@ -414,6 +418,15 @@ public final class Camera {
} }
} }
public func setZoomDelta(_ zoomDelta: CGFloat) {
self.queue.async {
if let context = self.contextRef?.takeUnretainedValue() {
context.setZoomDelta(zoomDelta)
}
}
}
public func setTorchActive(_ active: Bool) { public func setTorchActive(_ active: Bool) {
self.queue.async { self.queue.async {
if let context = self.contextRef?.takeUnretainedValue() { if let context = self.contextRef?.takeUnretainedValue() {

View File

@ -220,4 +220,13 @@ final class CameraDevice {
device.videoZoomFactor = max(1.0, min(10.0, zoomLevel)) device.videoZoomFactor = max(1.0, min(10.0, zoomLevel))
} }
} }
func setZoomDelta(_ zoomDelta: CGFloat) {
guard let device = self.videoDevice else {
return
}
self.transaction(device) { device in
device.videoZoomFactor = max(1.0, min(10.0, device.videoZoomFactor * zoomDelta))
}
}
} }

View File

@ -873,11 +873,10 @@ public class CameraScreen: ViewController {
@objc private func handlePinch(_ gestureRecognizer: UIPinchGestureRecognizer) { @objc private func handlePinch(_ gestureRecognizer: UIPinchGestureRecognizer) {
switch gestureRecognizer.state { switch gestureRecognizer.state {
case .began:
gestureRecognizer.scale = 1.0
case .changed: case .changed:
let scale = gestureRecognizer.scale let scale = gestureRecognizer.scale
self.camera.setZoomLevel(scale) self.camera.setZoomDelta(scale)
gestureRecognizer.scale = 1.0
default: default:
break break
} }

View File

@ -387,6 +387,7 @@ public final class MediaEditor {
self.setGradientColors([topColor, bottomColor]) self.setGradientColors([topColor, bottomColor])
if player == nil { if player == nil {
self.updateRenderChain()
self.maybeGeneratePersonSegmentation(image) self.maybeGeneratePersonSegmentation(image)
} }
@ -425,10 +426,20 @@ public final class MediaEditor {
} }
private var skipRendering = false private var skipRendering = false
private func updateValues(skipRendering: Bool = false, _ f: (MediaEditorValues) -> MediaEditorValues) {
if skipRendering {
self.skipRendering = true
}
self.values = f(self.values)
if skipRendering {
self.skipRendering = false
}
}
public func setCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) { public func setCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) {
self.skipRendering = true self.updateValues(skipRendering: true) { values in
self.values = self.values.withUpdatedCrop(offset: offset, scale: scale, rotation: rotation, mirroring: mirroring) return values.withUpdatedCrop(offset: offset, scale: scale, rotation: rotation, mirroring: mirroring)
self.skipRendering = false }
} }
public func getToolValue(_ key: EditorToolKey) -> Any? { public func getToolValue(_ key: EditorToolKey) -> Any? {
@ -436,19 +447,24 @@ public final class MediaEditor {
} }
public func setToolValue(_ key: EditorToolKey, value: Any) { public func setToolValue(_ key: EditorToolKey, value: Any) {
var updatedToolValues = self.values.toolValues self.updateValues { values in
updatedToolValues[key] = value var updatedToolValues = values.toolValues
self.values = self.values.withUpdatedToolValues(updatedToolValues) updatedToolValues[key] = value
self.updateRenderChain() return values.withUpdatedToolValues(updatedToolValues)
}
} }
public func setVideoIsMuted(_ videoIsMuted: Bool) { public func setVideoIsMuted(_ videoIsMuted: Bool) {
self.player?.isMuted = videoIsMuted self.player?.isMuted = videoIsMuted
self.values = self.values.withUpdatedVideoIsMuted(videoIsMuted) self.updateValues(skipRendering: true) { values in
return values.withUpdatedVideoIsMuted(videoIsMuted)
}
} }
public func setVideoIsFullHd(_ videoIsFullHd: Bool) { public func setVideoIsFullHd(_ videoIsFullHd: Bool) {
self.values = self.values.withUpdatedVideoIsFullHd(videoIsFullHd) self.updateValues(skipRendering: true) { values in
return values.withUpdatedVideoIsFullHd(videoIsFullHd)
}
} }
private var targetTimePosition: (CMTime, Bool)? private var targetTimePosition: (CMTime, Bool)?
@ -494,23 +510,29 @@ public final class MediaEditor {
}) })
} }
public func setVideoTrimRange(_ trimRange: Range<Double>) { public func setVideoTrimRange(_ trimRange: Range<Double>, apply: Bool) {
self.skipRendering = true self.updateValues(skipRendering: true) { values in
self.values = self.values.withUpdatedVideoTrimRange(trimRange) return values.withUpdatedVideoTrimRange(trimRange)
self.skipRendering = false }
self.player?.currentItem?.forwardPlaybackEndTime = CMTime(seconds: trimRange.upperBound, preferredTimescale: CMTimeScale(1000)) if apply {
self.player?.currentItem?.forwardPlaybackEndTime = CMTime(seconds: trimRange.upperBound, preferredTimescale: CMTimeScale(1000))
}
} }
public func setDrawingAndEntities(data: Data?, image: UIImage?, entities: [CodableDrawingEntity]) { public func setDrawingAndEntities(data: Data?, image: UIImage?, entities: [CodableDrawingEntity]) {
self.values = self.values.withUpdatedDrawingAndEntities(drawing: image, entities: entities) self.updateValues(skipRendering: true) { values in
return values.withUpdatedDrawingAndEntities(drawing: image, entities: entities)
}
} }
public func setGradientColors(_ gradientColors: [UIColor]) { public func setGradientColors(_ gradientColors: [UIColor]) {
self.values = self.values.withUpdatedGradientColors(gradientColors: gradientColors) self.updateValues(skipRendering: true) { values in
return values.withUpdatedGradientColors(gradientColors: gradientColors)
}
} }
private var previousUpdateTime = CACurrentMediaTime() private var previousUpdateTime: Double?
private var scheduledUpdate = false private var scheduledUpdate = false
private func updateRenderChain() { private func updateRenderChain() {
self.renderChain.update(values: self.values) self.renderChain.update(values: self.values)
@ -519,10 +541,9 @@ public final class MediaEditor {
let currentTime = CACurrentMediaTime() let currentTime = CACurrentMediaTime()
if !self.scheduledUpdate { if !self.scheduledUpdate {
let delay = 0.03333 let delay = 0.03333
let delta = currentTime - self.previousUpdateTime if let previousUpdateTime = self.previousUpdateTime, currentTime - previousUpdateTime < delay {
if delta < delay {
self.scheduledUpdate = true self.scheduledUpdate = true
Queue.mainQueue().after(delay - delta) { Queue.mainQueue().after(delay - (currentTime - previousUpdateTime)) {
self.scheduledUpdate = false self.scheduledUpdate = false
self.previousUpdateTime = CACurrentMediaTime() self.previousUpdateTime = CACurrentMediaTime()
self.renderer.willRenderFrame() self.renderer.willRenderFrame()

View File

@ -200,6 +200,8 @@ final class MediaEditorScreenComponent: Component {
private let textDoneButton = ComponentView<Empty>() private let textDoneButton = ComponentView<Empty>()
private let textSize = ComponentView<Empty>() private let textSize = ComponentView<Empty>()
private var isDismissed = false
private var component: MediaEditorScreenComponent? private var component: MediaEditorScreenComponent?
private weak var state: State? private weak var state: State?
private var environment: ViewControllerComponentContainer.Environment? private var environment: ViewControllerComponentContainer.Environment?
@ -274,6 +276,7 @@ final class MediaEditorScreenComponent: Component {
} }
func animateOut(to source: TransitionAnimationSource) { func animateOut(to source: TransitionAnimationSource) {
self.isDismissed = true
let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut)) let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
if let view = self.cancelButton.view { if let view = self.cancelButton.view {
transition.setAlpha(view: view, alpha: 0.0) transition.setAlpha(view: view, alpha: 0.0)
@ -398,6 +401,9 @@ final class MediaEditorScreenComponent: Component {
} }
func update(component: MediaEditorScreenComponent, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize { func update(component: MediaEditorScreenComponent, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
guard !self.isDismissed else {
return availableSize
}
let environment = environment[ViewControllerComponentContainer.Environment.self].value let environment = environment[ViewControllerComponentContainer.Environment.self].value
self.environment = environment self.environment = environment
@ -612,10 +618,9 @@ final class MediaEditorScreenComponent: Component {
framesUpdateTimestamp: playerState.framesUpdateTimestamp, framesUpdateTimestamp: playerState.framesUpdateTimestamp,
trimUpdated: { [weak mediaEditor] start, end, updatedEnd, done in trimUpdated: { [weak mediaEditor] start, end, updatedEnd, done in
if let mediaEditor { if let mediaEditor {
mediaEditor.setVideoTrimRange(start..<end, apply: done)
if done { if done {
mediaEditor.seek(start, andPlay: true) mediaEditor.seek(start, andPlay: true)
mediaEditor.setVideoTrimRange(start..<end)
} else { } else {
mediaEditor.seek(updatedEnd ? end : start, andPlay: false) mediaEditor.seek(updatedEnd ? end : start, andPlay: false)
} }
@ -1106,9 +1111,10 @@ public final class MediaEditorScreen: ViewController {
private var isDisplayingTool = false private var isDisplayingTool = false
private var isInteractingWithEntities = false private var isInteractingWithEntities = false
private var isEnhacing = false
private var isDismissing = false private var isDismissing = false
private var dismissOffset: CGFloat = 0.0 private var dismissOffset: CGFloat = 0.0
private var isEnhacing = false private var isDismissed = false
private var presentationData: PresentationData private var presentationData: PresentationData
private var validLayout: ContainerViewLayout? private var validLayout: ContainerViewLayout?
@ -1259,22 +1265,6 @@ public final class MediaEditorScreen: ViewController {
return return
} }
var hasSwipeToDismiss = false
if let subject = self.subject {
if case .asset = subject {
hasSwipeToDismiss = true
} else if case .draft = subject {
hasSwipeToDismiss = true
}
}
if hasSwipeToDismiss {
let dismissPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handleDismissPan(_:)))
dismissPanGestureRecognizer.delegate = self
dismissPanGestureRecognizer.maximumNumberOfTouches = 1
self.previewContainerView.addGestureRecognizer(dismissPanGestureRecognizer)
self.dismissPanGestureRecognizer = dismissPanGestureRecognizer
}
let mediaDimensions = subject.dimensions let mediaDimensions = subject.dimensions
let maxSide: CGFloat = 1920.0 / UIScreen.main.scale let maxSide: CGFloat = 1920.0 / UIScreen.main.scale
let fittedSize = mediaDimensions.cgSize.fitted(CGSize(width: maxSide, height: maxSide)) let fittedSize = mediaDimensions.cgSize.fitted(CGSize(width: maxSide, height: maxSide))
@ -1346,6 +1336,12 @@ public final class MediaEditorScreen: ViewController {
self.view.disablesInteractiveModalDismiss = true self.view.disablesInteractiveModalDismiss = true
self.view.disablesInteractiveKeyboardGestureRecognizer = true self.view.disablesInteractiveKeyboardGestureRecognizer = true
let dismissPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handleDismissPan(_:)))
dismissPanGestureRecognizer.delegate = self
dismissPanGestureRecognizer.maximumNumberOfTouches = 1
self.previewContainerView.addGestureRecognizer(dismissPanGestureRecognizer)
self.dismissPanGestureRecognizer = dismissPanGestureRecognizer
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:))) let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
panGestureRecognizer.delegate = self panGestureRecognizer.delegate = self
panGestureRecognizer.minimumNumberOfTouches = 2 panGestureRecognizer.minimumNumberOfTouches = 2
@ -1429,11 +1425,20 @@ public final class MediaEditorScreen: ViewController {
return return
} }
var hasSwipeToDismiss = false
if let subject = self.subject {
if case .asset = subject {
hasSwipeToDismiss = true
} else if case .draft = subject {
hasSwipeToDismiss = true
}
}
let translation = gestureRecognizer.translation(in: self.view) let translation = gestureRecognizer.translation(in: self.view)
let velocity = gestureRecognizer.velocity(in: self.view) let velocity = gestureRecognizer.velocity(in: self.view)
switch gestureRecognizer.state { switch gestureRecognizer.state {
case .changed: case .changed:
if abs(translation.y) > 10.0 && !self.isEnhacing { if abs(translation.y) > 10.0 && !self.isEnhacing && hasSwipeToDismiss {
if !self.isDismissing { if !self.isDismissing {
self.isDismissing = true self.isDismissing = true
controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut)) controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut))
@ -1449,7 +1454,7 @@ public final class MediaEditorScreen: ViewController {
} else if self.isEnhacing { } else if self.isEnhacing {
if let mediaEditor = self.mediaEditor { if let mediaEditor = self.mediaEditor {
let value = mediaEditor.getToolValue(.enhance) as? Float ?? 0.0 let value = mediaEditor.getToolValue(.enhance) as? Float ?? 0.0
let delta = Float((translation.x / self.frame.width) * 2.0) let delta = Float((translation.x / self.frame.width) * 1.5)
let updatedValue = max(0.0, min(1.0, value + delta)) let updatedValue = max(0.0, min(1.0, value + delta))
mediaEditor.setToolValue(.enhance, value: updatedValue) mediaEditor.setToolValue(.enhance, value: updatedValue)
} }
@ -1573,6 +1578,7 @@ public final class MediaEditorScreen: ViewController {
guard let controller = self.controller else { guard let controller = self.controller else {
return return
} }
self.isDismissed = true
controller.statusBar.statusBarStyle = .Ignore controller.statusBar.statusBarStyle = .Ignore
let previousDimAlpha = self.backgroundDimView.alpha let previousDimAlpha = self.backgroundDimView.alpha
@ -1825,7 +1831,7 @@ public final class MediaEditorScreen: ViewController {
private var drawingScreen: DrawingScreen? private var drawingScreen: DrawingScreen?
func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, animateOut: Bool = false, transition: Transition) { func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, animateOut: Bool = false, transition: Transition) {
guard let controller = self.controller else { guard let controller = self.controller, !self.isDismissed else {
return return
} }
let isFirstTime = self.validLayout == nil let isFirstTime = self.validLayout == nil
@ -2800,6 +2806,8 @@ private final class ToolValueComponent: Component {
private let title = ComponentView<Empty>() private let title = ComponentView<Empty>()
private let value = ComponentView<Empty>() private let value = ComponentView<Empty>()
private let hapticFeedback = HapticFeedback()
private var component: ToolValueComponent? private var component: ToolValueComponent?
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
@ -2814,6 +2822,8 @@ private final class ToolValueComponent: Component {
} }
func update(component: ToolValueComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize { func update(component: ToolValueComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let previousValue = self.component?.value
self.component = component self.component = component
self.state = state self.state = state
@ -2860,6 +2870,10 @@ private final class ToolValueComponent: Component {
transition.setPosition(view: valueView, position: valueFrame.center) transition.setPosition(view: valueView, position: valueFrame.center)
transition.setBounds(view: valueView, bounds: CGRect(origin: .zero, size: valueFrame.size)) transition.setBounds(view: valueView, bounds: CGRect(origin: .zero, size: valueFrame.size))
} }
if let previousValue, component.value != previousValue, self.alpha > 0.0 {
self.hapticFeedback.impact(.click05)
}
return availableSize return availableSize
} }