У меня есть простой AVCaptureSession для получения камеры в моем приложении и фотографирования. Как я могу реализовать функцию «увеличение масштаба», используя UIGestureRecognizer
для камеры?
AVCaptureDevice Camera Zoom
Ответы (9)
Принятый ответ на самом деле устарел, и я не уверен, что он действительно сделает фотографию увеличенного изображения. Существует способ увеличить масштаб, как говорит ответ 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);
}
}
}
Я обнаружил, что добавление функции арктангенса к скорости немного облегчит эффект увеличения и уменьшения масштаба. Это не совсем идеально, но эффект достаточно хорош для нужд. Вероятно, может быть другая функция, позволяющая уменьшить масштаб, когда он почти достигает 1.
ПРИМЕЧАНИЕ. Кроме того, масштаб жеста щипка изменяется от 0 до бесконечности, где от 0 до 1 означает сжатие (уменьшение масштаба), а от 1 до бесконечности — сжатие (увеличение). Чтобы получить хороший эффект увеличения и уменьшения масштаба, вам понадобится математическое уравнение. Скорость на самом деле от -бесконечно до бесконечности с 0 в качестве отправной точки.
EDIT: исправлен сбой при исключении диапазона. Спасибо @garafajon!
Многие пытались сделать это, установив для свойства преобразования слоя значение CGAffineTransformMakeScale(gesture.scale.x, gesture.scale.y);
См. здесь для полноценной реализации масштабирования.
Начиная с iOS 7 вы можете установить масштаб напрямую с помощью videoZoomFactor
свойства AVCaptureDevice
.
Привяжите свойство scale
элемента UIPinchGestureRecognizer
к элементу 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 4
Добавьте распознаватель жестов щипка на самый передний план и подключите его к этому действию (pinchToZoom). captureDevice должен быть экземпляром, который в данный момент предоставляет входные данные для сеанса захвата. 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
}
}
Будет полезно объявить zoomFactor на вашей камере или vc. Я обычно ставлю его на тот же синглтон, что и AVCaptureSession. Это будет действовать как значение по умолчанию для videoZoomFactor устройства захвата.
var zoomFactor: Float = 1.0
В быстрой версии вы можете увеличивать/уменьшать масштаб, просто передавая масштабированное число в 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 Cartier (спасибо). В своем коде я предпочитаю использовать более плавный метод рамптовидеозумфактор и более простой способ вычисления коэффициента масштабирования устройства.
(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 Cartier:
- (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];
}
}];