Darren
Darren

Reputation: 10398

AVFoundation photo size and rotation

I'm having a nightmare time trying to correct a photo taken with AVFoundation captureStillImageAsynchronouslyFromConnection to size and orient to exactly what is shown on the screen.

I show the AVCaptureVideoPreviewLayer with this code to make sure it displays the correct way up at all rotations:

previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
[previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
previewLayer.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height);
if ([[previewLayer connection] isVideoOrientationSupported])
{
    [[previewLayer connection] setVideoOrientation:(AVCaptureVideoOrientation)[UIApplication sharedApplication].statusBarOrientation];
}
[self.view.layer insertSublayer:previewLayer atIndex:0];

Now when I have a returned image it needs cropping as it's much bigger than what was displayed. I know there are loads of UIImage cropping examples, but the first hurdle I seem to have is finding the correct CGRect to use. When I simply crop to self.view.frame the image is cropped at the wrong location.

The preview is using AVLayerVideoGravityResizeAspectFill and I have my UIImageView also set to AspectFill

So how can I get the correct frame that AVFoundation is displaying on screen from the preview layer?

EDIT ----

Here's an example of the problem i'm facing. Using the front camera of an iPad Mini, the camera using the resolution 720x1280 but the display is 768x0124. The view displays this (See the dado rail at the top of the image:

enter image description here

Then when I take the image and display it, it looks like this:

enter image description here

Obviously the camera display was centred in the view, but the cropped image is taken from the top(none seen) section of the photo.

Upvotes: 4

Views: 2809

Answers (1)

Richard Shin
Richard Shin

Reputation: 663

I'm working on a similar project right now and thought I might be able to help, if you haven't already figured this out.

the first hurdle I seem to have is finding the correct CGRect to use. When I simply crop to self.view.frame the image is cropped at the wrong location.

Let's say your image is 720x1280 and you want your image to be cropped to the rectangle of your display, which is a CGRect of size 768x1024. You can't just pass a rectangle of size 768x1024. First, your image isn't 768 pixels wide. Second, you need to specify the placement of that rectangle with respects to the image (i.e. by specifying the rectangle's origin point). In your example, self.view.frame is a CGRect that has an origin of (0, 0). That's why it's always cropping from the top of your image rather than from the center.

Calculating the cropping rectangle is a bit tricky because you have a few different coordinate systems.

  • You've got your view controller's view, which has...
  • ...a video preview layer as a sublayer, which is displaying an aspect-filled image, but...
  • ...the AVCaptureOutput returns a UIImage that not only has a different width/height than the video preview, but it also has a different aspect ratio.

So because your preview layer is displaying a centered and cropped preview image (i.e. aspect fill), what you basically want to find is the CGRect that:

  • Has the same display ratio as self.view.bounds
  • Has the same smaller dimension size as the smaller dimension of the UIImage (i.e. aspect fit)
  • Is centered in the UIImage

So something like this:

// Determine the width:height ratio of the crop rect, based on self.bounds
CGFloat widthToHeightRatio = self.bounds.size.width / self.bounds.size.height;
CGRect cropRect;
// Set the crop rect's smaller dimension to match the image's smaller dimension, and
// scale its other dimension according to the width:height ratio.
if (image.size.width < image.size.height) {
    cropRect.size.width = image.size.width;
    cropRect.size.height = cropRect.size.width / widthToHeightRatio;
} else {
    cropRect.size.width = image.size.height * widthToHeightRatio;
    cropRect.size.height = image.size.height;
}
// Center the rect in the longer dimension
if (cropRect.size.width < cropRect.size.height) {
    cropRect.origin.x = 0;
    cropRect.origin.y = (image.size.height - cropRect.size.height) / 2.0;
} else {
    cropRect.origin.x = (image.size.width - cropRect.size.width) / 2.0;
    cropRect.origin.y = 0;
}

So finally, to go back to your original example where the image is 720x1280, and you want your image to be cropped to the rectangle of your display which is 768x1024, you will end up with a CGRect of size 720x960, with an origin of x = 0, y = 1280-960/2 = 160.

Upvotes: 3

Related Questions