我有一个简单的AVCaptureSession来在我的应用程序中获取相机源并拍照。如何使用相机的UIGestureRecognizer
实现"捏合变焦"功能?
接受的答案实际上已经过时了,我不确定它是否真的会拍摄放大图像的照片。有一种放大方法,就像 bcattle 答案所说的那样。他的答案的问题在于,它不负责用户可以放大然后从该缩放位置重新启动的事实。他的解决方案将创造某种并不真正优雅的跳跃。
执行此操作的最简单和最优雅的方法是使用捏合手势的速度。
-(void) handlePinchToZoomRecognizer:(UIPinchGestureRecognizer*)pinchRecognizer {
const CGFloat pinchVelocityDividerFactor = 5.0f;
if (pinchRecognizer.state == UIGestureRecognizerStateChanged) {
NSError *error = nil;
if ([videoDevice lockForConfiguration:&error]) {
CGFloat desiredZoomFactor = device.videoZoomFactor + atan2f(pinchRecognizer.velocity, pinchVelocityDividerFactor);
// Check if desiredZoomFactor fits required range from 1.0 to activeFormat.videoMaxZoomFactor
device.videoZoomFactor = MAX(1.0, MIN(desiredZoomFactor, device.activeFormat.videoMaxZoomFactor));
[videoDevice unlockForConfiguration];
} else {
NSLog(@"error: %@", error);
}
}
}
我发现将 arctan 函数添加到速度中会稍微缓解放大缩小效果。它并不完全完美,但效果足以满足需求。可能还有另一个功能可以在几乎达到 1 时简化缩小。
注意:此外,捏合手势的比例从 0 变为无限,0 到 1 表示收缩(缩小),1 表示无限收缩(放大)。要获得良好的放大缩小效果,您需要有一个数学方程式。速度实际上是从 -无限到无限,0 是起点。
编辑:修复了范围异常崩溃的问题。感谢@garafajon!
Swift 4
将捏合手势识别器添加到最前面的视图,并将其连接到此操作 (pinchToZoom)。捕获设备应该是当前为捕获会话提供输入的实例。捏合缩放为前后捕获设备提供平滑缩放。
@IBAction func pinchToZoom(_ pinch: UIPinchGestureRecognizer) {
guard let device = captureDevice else { return }
func minMaxZoom(_ factor: CGFloat) -> CGFloat { return min(max(factor, 1.0), device.activeFormat.videoMaxZoomFactor) }
func update(scale factor: CGFloat) {
do {
try device.lockForConfiguration()
defer { device.unlockForConfiguration() }
device.videoZoomFactor = factor
} catch {
debugPrint(error)
}
}
let newScaleFactor = minMaxZoom(pinch.scale * zoomFactor)
switch sender.state {
case .began: fallthrough
case .changed: update(scale: newScaleFactor)
case .ended:
zoomFactor = minMaxZoom(newScaleFactor)
update(scale: zoomFactor)
default: break
}
}
在相机或 vc 上声明 zoomFactor 会很有用。我通常把它放在具有AVCaptureSession的同一个单例上。这将充当捕获设备的videoZoomFactor的默认值。
var zoomFactor: Float = 1.0
许多人试图通过将层上的 transform 属性设置为 CGAffineTransformMakeScale(gesture.scale.x, gesture.scale.y);
请参阅此处了解捏合缩放的完整实现。
从 iOS 7 开始,您可以使用 videoZoomFactor
属性直接设置缩放 AVCaptureDevice
.
使用缩放常量将UIPinchGestureRecognizer
的scale
属性绑定到videoZoomFactor
。这将让你改变对味道的敏感性:
-(void) handlePinchToZoomRecognizer:(UIPinchGestureRecognizer*)pinchRecognizer {
const CGFloat pinchZoomScaleFactor = 2.0;
if (pinchRecognizer.state == UIGestureRecognizerStateChanged) {
NSError *error = nil;
if ([videoDevice lockForConfiguration:&error]) {
videoDevice.videoZoomFactor = 1.0 + pinchRecognizer.scale * pinchZoomScaleFactor;
[videoDevice unlockForConfiguration];
} else {
NSLog(@"error: %@", error);
}
}
}
注意AVCaptureDevice
,以及AVCaptureSession
相关的其他所有内容,都不是线程安全的。因此,您可能不想从主队列执行此操作。
在 swift 版本中,您可以通过简单地在 videoZoomFactor 上传递缩放数字来放大/缩小。UIPinchGestureRecognizer 处理程序中的以下代码将解决此问题。
do {
try device.lockForConfiguration()
switch gesture.state {
case .began:
self.pivotPinchScale = device.videoZoomFactor
case .changed:
var factor = self.pivotPinchScale * gesture.scale
factor = max(1, min(factor, device.activeFormat.videoMaxZoomFactor))
device.videoZoomFactor = factor
default:
break
}
device.unlockForConfiguration()
} catch {
// handle exception
}
在这里,pivotPinchScale 是一个 CGFloat 属性,它在控制器的某个地方声明。
您还可以参考以下项目,了解相机如何与UIPinchGestureRecognizer配合使用。https://github.com/DragonCherry/CameraPreviewController
我从卡地亚的解决方案@Gabriel开始(谢谢)。在我的代码中,我更喜欢使用更平滑的渐变到视频缩放因子和更简单的方法来计算设备的比例因子。
(IBAction) pinchForZoom:(id) sender forEvent:(UIEvent*) event {
UIPinchGestureRecognizer* pinchRecognizer = (UIPinchGestureRecognizer *)sender;
static CGFloat zoomFactorBegin = .0;
if ( UIGestureRecognizerStateBegan == pinchRecognizer.state ) {
zoomFactorBegin = self.captureDevice.videoZoomFactor;
} else if (UIGestureRecognizerStateChanged == pinchRecognizer.state) {
NSError *error = nil;
if ([self.captureDevice lockForConfiguration:&error]) {
CGFloat desiredZoomFactor = zoomFactorBegin * pinchRecognizer.scale;
CGFloat zoomFactor = MAX(1.0, MIN(desiredZoomFactor, self.captureDevice.activeFormat.videoMaxZoomFactor));
[self.captureDevice rampToVideoZoomFactor:zoomFactor withRate:3.0];
[self.captureDevice unlockForConfiguration];
} else {
NSLog(@"error: %@", error);
}
}
}
更简单的方法可以使用捏部识别器处理相机缩放级别。您唯一需要做的就是获取cameraDevice.videoZoomFactor
并将其设置为.began
状态的识别器,如下所示
@objc private func viewPinched(recognizer: UIPinchGestureRecognizer) {
switch recognizer.state {
case .began:
recognizer.scale = cameraDevice.videoZoomFactor
case .changed:
let scale = recognizer.scale
do {
try cameraDevice.lockForConfiguration()
cameraDevice.videoZoomFactor = max(cameraDevice.minAvailableVideoZoomFactor, min(scale, cameraDevice.maxAvailableVideoZoomFactor))
cameraDevice.unlockForConfiguration()
}
catch {
print(error)
}
default:
break
}
}
基于@Gabriel卡地亚的回答:
- (void) cameraZoomWithPinchVelocity: (CGFloat)velocity {
CGFloat pinchVelocityDividerFactor = 40.0f;
if (velocity < 0) {
pinchVelocityDividerFactor = 5.; //zoom in
}
if (_videoInput) {
if([[_videoInput device] position] == AVCaptureDevicePositionBack) {
NSError *error = nil;
if ([[_videoInput device] lockForConfiguration:&error]) {
CGFloat desiredZoomFactor = [_videoInput device].videoZoomFactor + atan2f(velocity, pinchVelocityDividerFactor);
// Check if desiredZoomFactor fits required range from 1.0 to activeFormat.videoMaxZoomFactor
CGFloat maxFactor = MIN(10, [_videoInput device].activeFormat.videoMaxZoomFactor);
[_videoInput device].videoZoomFactor = MAX(1.0, MIN(desiredZoomFactor, maxFactor));
[[_videoInput device] unlockForConfiguration];
} else {
NSLog(@"cameraZoomWithPinchVelocity error: %@", error);
}
}
}
}
我正在使用iOS SDK 8.3和AVfoundation框架,对我来说使用以下方法工作:
nameOfAVCaptureVideoPreviewLayer.affineTransform = CGAffineTransformMakeScale(scaleX, scaleY)
为了以相同的比例保存图片,我使用以下方法:
nameOfAVCaptureConnection.videoScaleAndCropFactor = factorNumber;
下面的代码用于获取比例中的图像
[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if(imageDataSampleBuffer != NULL){
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage *image = [UIImage imageWithData:imageData];
}
}];