Recortar una image capturada exactamente como se ve en AVCaptureVideoPreviewLayer

Tengo una aplicación de fotografía que usa AV Foundation. He configurado una capa de vista previa usando AVCaptureVideoPreviewLayer que ocupa la mitad superior de la pantalla. Entonces, cuando el usuario intenta tomar su foto, todo lo que pueden ver es lo que ve la mitad superior de la pantalla.

Esto funciona muy bien, pero cuando el usuario toma la foto y trato de configurar la foto como el contenido de la capa, la image está distorsionada. Investigué y me di count de que tendría que recortar la image.

Todo lo que quiero hacer es recortar la image capturada completa para que todo lo que queda sea exactamente lo que el usuario podría ver originalmente en la mitad superior de la pantalla.

He podido orderar esto, pero estoy haciendo esto ingresando los valores de CGRect manuales y todavía no se ve perfecto. Tiene que haber una forma más fácil de hacerlo.

Literalmente he revisado todas las publicaciones sobre el desbordamiento de stack durante los últimos 2 días sobre recortar imágenes y nada ha funcionado.

Tiene que haber una manera de recortar mediante progtwigción la image capturada para que la image final sea exactamente lo que se vio originalmente en la capa de vista previa.

Aquí está mi implementación viewDidLoad:

- (void)viewDidLoad { [super viewDidLoad]; AVCaptureSession *session =[[AVCaptureSession alloc]init]; [session setSessionPreset:AVCaptureSessionPresetPhoto]; AVCaptureDevice *inputDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; NSError *error = [[NSError alloc]init]; AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:inputDevice error:&error]; if([session canAddInput:deviceInput]) [session addInput:deviceInput]; CALayer *rootLayer = [[self view]layer]; [rootLayer setMasksToBounds:YES]; _previewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:session]; [_previewLayer setFrame:CGRectMake(0, 0, rootLayer.bounds.size.width, rootLayer.bounds.size.height/2)]; [_previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill]; [rootLayer insertSublayer:_previewLayer atIndex:0]; _stillImageOutput = [[AVCaptureStillImageOutput alloc] init]; [session addOutput:_stillImageOutput]; [session startRunning]; } 

Y aquí está el código que se ejecuta cuando el usuario presiona el button para capturar una foto:

 -(IBAction)stillImageCapture { AVCaptureConnection *videoConnection = nil; for (AVCaptureConnection *connection in _stillImageOutput.connections){ for (AVCaptureInputPort *port in [connection inputPorts]){ if ([[port mediaType] isEqual:AVMediaTypeVideo]){ videoConnection = connection; break; } } if (videoConnection) { break; } } NSLog(@"about to request a capture from: %@", _stillImageOutput); [_stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { if(imageDataSampleBuffer) { NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer]; UIImage *image = [[UIImage alloc]initWithData:imageData]; CALayer *subLayer = [CALayer layer]; subLayer.frame = _previewLayer.frame; image = [self rotate:image andOrientation:image.imageOrientation]; //Below is the crop that is sort of working for me, but as you can see I am manually entering in values and just guessing and it still does not look perfect. CGRect cropRect = CGRectMake(0, 650, 3000, 2000); CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], cropRect); subLayer.contents = (id)[UIImage imageWithCGImage:imageRef].CGImage; subLayer.frame = _previewLayer.frame; [_previewLayer addSublayer:subLayer]; } }]; } 

Eche un vistazo a AVCaptureVideoPreviewLayer s

 -(CGRect)metadataOutputRectOfInterestForRect:(CGRect)layerRect 

Este método le permite convertir fácilmente el CGRect visible de su capa a la salida real de la camera.

Una advertencia: la camera física no está montada "en la parte superior", sino que gira 90 grados en el sentido de las agujas del reloj. (Por lo tanto, si mantiene presionado su iPhone – Botón Inicio, la camera está realmente hacia arriba).

Teniendo esto en count, debe convertir el CGRect que el método anterior le brinda, para recortar la image exactamente en la pantalla.

Ejemplo:

 CGRect visibleLayerFrame = THE ACTUAL VISIBLE AREA IN THE LAYER FRAME CGRect metaRect = [self.previewView.layer metadataOutputRectOfInterestForRect:visibleLayerFrame]; CGSize originalSize = [originalImage size]; if (UIInterfaceOrientationIsPortrait(_snapInterfaceOrientation)) { // For portrait images, swap the size of the image, because // here the output image is actually rotated relative to what you see on screen. CGFloat temp = originalSize.width; originalSize.width = originalSize.height; originalSize.height = temp; } // metaRect is fractional, that's why we multiply here CGRect cropRect; cropRect.origin.x = metaRect.origin.x * originalSize.width; cropRect.origin.y = metaRect.origin.y * originalSize.height; cropRect.size.width = metaRect.size.width * originalSize.width; cropRect.size.height = metaRect.size.height * originalSize.height; cropRect = CGRectIntegral(cropRect); 

Esto puede ser un poco confuso, pero lo que realmente me hizo entender es esto:

Mantenga su dispositivo "Botón Inicio derecho" -> Verá que el eje x realmente se encuentra a lo largo de la "altura" de su iPhone, mientras que el eje y se encuentra a lo largo del "ancho" de su iPhone. Es por eso que para las imágenes de retrato, tienes que cambiar el tamaño;)

@Cabus tiene una solución que funciona y debe votar en voz alta su respuesta. Sin embargo, hice mi propia versión en Swift con lo siguiente:

 // The image returned in initialImageData will be larger than what // is shown in the AVCaptureVideoPreviewLayer, so we need to crop it. let image : UIImage = UIImage(data: initialImageData)! let originalSize : CGSize let visibleLayerFrame = self.previewView!.bounds // THE ACTUAL VISIBLE AREA IN THE LAYER FRAME // Calculate the fractional size that is shown in the preview let metaRect : CGRect = (self.videoPreviewLayer?.metadataOutputRectOfInterestForRect(visibleLayerFrame))! if (image.imageOrientation == UIImageOrientation.Left || image.imageOrientation == UIImageOrientation.Right) { // For these images (which are portrait), swap the size of the // image, because here the output image is actually rotated // relative to what you see on screen. originalSize = CGSize(width: image.size.height, height: image.size.width) } else { originalSize = image.size } // metaRect is fractional, that's why we multiply here. let cropRect : CGRect = CGRectIntegral( CGRect( x: metaRect.origin.x * originalSize.width, y: metaRect.origin.y * originalSize.height, width: metaRect.size.width * originalSize.width, height: metaRect.size.height * originalSize.height)) let finalImage : UIImage = UIImage(CGImage: CGImageCreateWithImageInRect(image.CGImage, cropRect)!, scale:1, orientation: image.imageOrientation ) 

Aquí está la respuesta de @Erik Allen en Swift 3 :

 let originalSize: CGSize let visibleLayerFrame = self?.photoView.bounds // Calculate the fractional size that is shown in the preview let metaRect = (self?.videoPreviewLayer?.metadataOutputRectOfInterest(for: visibleLayerFrame ?? CGRect.zero)) ?? CGRect.zero if (image.imageOrientation == UIImageOrientation.left || image.imageOrientation == UIImageOrientation.right) { // For these images (which are portrait), swap the size of the // image, because here the output image is actually rotated // relative to what you see on screen. originalSize = CGSize(width: image.size.height, height: image.size.width) } else { originalSize = image.size } let cropRect: CGRect = CGRect(x: metaRect.origin.x * originalSize.width, y: metaRect.origin.y * originalSize.height, width: metaRect.size.width * originalSize.width, height: metaRect.size.height * originalSize.height).integral if let finalCgImage = image.cgImage?.cropping(to: cropRect) { let finalImage = UIImage(cgImage: finalCgImage, scale: 1.0, orientation: image.imageOrientation) // User your image... }