I am having issues with my AVCaptureVideoPreviewLayer method when grabbing a cropped UIImage of the viewable screen. Currently it is working but not outputting the correct crop that I require.
I am trying to output a square but it (by the looks of it) seems to be giving the full height and compressing the image.
The before image shows the LIVE screen and the after image shows the image once the capture button has been pressed. You can see that it has been changed vertically to fit the square but the height hasn't been cropped vertically.
Capture Image Code
[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
if (imageSampleBuffer != NULL) {
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
[self processImage:[UIImage imageWithData:imageData]];
}
}];
Cropping code
- (void) processImage:(UIImage *)image { //process captured image, crop, resize and rotate
haveImage = YES;
CGRect deviceScreen = _previewLayer.bounds;
CGFloat width = deviceScreen.size.width;
CGFloat height = deviceScreen.size.height;
NSLog(#"WIDTH %f", width); // Outputing 320
NSLog(#"HEIGHT %f", height); // Outputting 320
UIGraphicsBeginImageContext(CGSizeMake(width, width));
[image drawInRect: CGRectMake(0, 0, width, width)];
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGRect cropRect = CGRectMake(0, 0, width, width);
CGImageRef imageRef = CGImageCreateWithImageInRect([smallImage CGImage], cropRect);
CGImageRelease(imageRef);
[captureImageGrab setImage:[UIImage imageWithCGImage:imageRef]];
}
Have you tried to set videoGravity?
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
Even if the preview layer is square, keep in mind that the generated still image keeps its original size.
From what I see, the problem is here:
UIGraphicsBeginImageContext(CGSizeMake(width, width));
[image drawInRect: CGRectMake(0, 0, width, width)];
You already made your context square with the first line. You still need to draw your image in its original format, it will be clipped by that context. On the second line, you are forcing the drawing of the original image in a square, thus making it look "squeezed".
You should find the right image height that keeps the original ratio while fitting your "width". Next, you will want to draw that image with the correct size (keeping the original ratio) in your square context. If you want to clip the center, change your Y position of the drawing.
Something similar to this:
- (void) processImage:(UIImage *)image {
UIGraphicsBeginImageContext(CGSizeMake(width, width));
CGFloat imageHeight = floorf(width / image.width * image.height);
CGFloat offsetY = floorf((imageHeight - width) / 2.0f);
[image drawInRect: CGRectMake(0, -offsetY, width, imageHeight)];
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[captureImageGrab setImage:smallImage];
}
That should do it.
Looking at the code it appears to me that you are drawing the image into a square. This is the part that shrinks the image without regard to the aspect ratio.
Instead, do something like this:
- (void) processImage:(UIImage *)image { //process captured image, crop, resize and rotate
haveImage = YES;
CGRect deviceScreen = _previewLayer.bounds;
CGFloat width = deviceScreen.size.width;
CGFloat height = deviceScreen.size.height;
NSLog(#"WIDTH %f", width); // Outputing 320
NSLog(#"HEIGHT %f", height); // Outputting 320
CGRect cropRect = CGRectZero;
if (image.size.width > image.size.height) {
cropRect.origin.x = (image.size.width-image.size.height)/2;
cropRect.size = CGSizeMake(image.size.height, image.size.height);
} else {
cropRect.origin.y = (image.size.height-image.size.width)/2;
cropRect.size = CGSizeMake(image.size.width, image.size.width);
}
CGImageRef croppedImage = CGImageCreateWithImageInRect(image.CGImage, cropRect);
UIGraphicsBeginImageContext(CGSizeMake(width, width));
[[UIImage imageWithCGImage:croppedImage] drawInRect:CGRectMake(0, 0, width, width)];
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRelease(croppedImage);
[captureImageGrab setImage:[UIImage imageWithCGImage:smallImage.CGImage scale:image.scale orientation:image.imageOrientation]];
}
The aspect ratio is distorted because the image captured by the camera is not square. When drawing it into a square it will be drawn with a distorted aspect ratio. A possible solution is to draw the image into the square preserving the aspect ration like:
- (void) processImage:(UIImage *)image { //process captured image, crop, resize and rotate
haveImage = YES;
CGRect deviceScreen = _previewLayer.bounds;
CGFloat width = deviceScreen.size.width;
CGFloat height = deviceScreen.size.height;
NSLog(#"WIDTH %f", width); // Outputing 320
NSLog(#"HEIGHT %f", height); // Outputting 320
UIGraphicsBeginImageContext(CGSizeMake(width, width));
CGFloat aspect_h = image.size.height * width / image.size.width;
[image drawInRect: CGRectMake(0, -(aspect_h - width)/2.0, width, aspect_h)];
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGRect cropRect = CGRectMake(0, 0, width, width);
CGImageRef imageRef = CGImageCreateWithImageInRect([smallImage CGImage], cropRect);
CGImageRelease(imageRef);
}
the new image will be of width width. For that width the height that will preserve the aspect ratio is aspect_h. Then, the image is shifted along the y axis to center it vertically.
Notice that if your app were to work on orientations that are not portrait a little more work is needed.
Another observation: you can get a better resolution image if you use image.size.width as the width for the new image context.
hope this helps!
My previewLayer is square and this did nothing for me:
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
#Rufel's answer is what WORKED!!! Here it is in Swift 5
func processImageIntoSquare(image: UIImage) -> UIImage? {
guard let previewLayer = previewLayer else { return nil }
let width = previewLayer.bounds.size.width
let size = CGSize(width: width, height: width)
UIGraphicsBeginImageContext(size)
let imageHeight = floor(width / image.size.width * image.size.height)
let offSetY = floor((imageHeight - width) / 2.0)
let rect = CGRect(x: 0, y: -offSetY, width: width, height: imageHeight)
image.draw(in: rect)
let smallImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return smallImage
}
And here is how I use it:
MyCameraController: AVCapturePhotoCaptureDelegate {
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
if let error = error { return }
guard let imageData = photo.fileDataRepresentation(), let imageFromCamera = UIImage(data: imageData) else { return }
guard let squareImage = processImageIntoSquare(image: imageFromCamera) else { return }
DispatchQueue.main.async { [weak self] in
self?.myImageView.image = squareImage
}
}
}
Related
I am using the following crop method to crop a uiimage that's sitting in a UIImageView which is then sitting in a UIScrollView.
-(UIImage *)cropImage:(UIImage *)image
{
float scale = 1.0f/_scrollView.zoomScale;
NSLog(#"Oh and heres that zoomScale: %f", _scrollView.zoomScale);
CGRect visibleRect;
visibleRect.origin.x = _scrollView.contentOffset.x * scale;
visibleRect.origin.y = _scrollView.contentOffset.y * scale;
visibleRect.size.width = _scrollView.bounds.size.width * scale;
visibleRect.size.height = _scrollView.bounds.size.height * scale;
NSLog(#"Oh and here's that CGRect: %f", visibleRect.origin.x);
NSLog(#"Oh and here's that CGRect: %f", visibleRect.origin.y);
NSLog(#"Oh and here's that CGRect: %f", visibleRect.size.width);
NSLog(#"Oh and here's that CGRect: %f", visibleRect.size.height);
CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], visibleRect);
UIImage *croppedImage = [[UIImage alloc] initWithCGImage:imageRef];
CGImageRelease(imageRef);
return croppedImage;
}
I need the image to be cropped to a CGSize of (321,115). Upon cropping the image and seeing the print results, I can see that visibleRect is (0,0,321,115) - what it is supposed to be, the croppedImage UIImage then has width:321 and height:115. for some reason however the image appears to be zoomed in entirely too far (the method cropped a smaller portion of the original image to a size of 321x115).
Why is this method not correctly cropping my image?
-As a side note: When I call this method, I am calling like so _croppedImage = [self cropImage:_imageView.image]; which sets a UIImage property of a custom UIView class to the cropped image.
Please try this function. It may help you.
Parameters:
UIImage
CGSize (321,115) or any size
// crop image - image will crop from full image
- (UIImage *)cropImageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {
double ratio;
double delta;
CGPoint offset;
//make a new square size, that is the resized imaged width
CGSize sz = CGSizeMake(newSize.width, newSize.width);
//figure out if the picture is landscape or portrait, then
//calculate scale factor and offset
if (image.size.width > image.size.height) {
ratio = newSize.width / image.size.width;
delta = (ratio*image.size.width - ratio*image.size.height);
offset = CGPointMake(delta/2, 0);
}
else {
ratio = newSize.width / image.size.height;
delta = (ratio*image.size.height - ratio*image.size.width);
offset = CGPointMake(0, delta/2);
}
//make the final clipping rect based on the calculated values
CGRect clipRect = CGRectMake(-offset.x,
-offset.y,
(ratio * image.size.width) + delta,
(ratio * image.size.height) + delta);
//start a new context, with scale factor 0.0 so retina displays get
//high quality image
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)]) {
UIGraphicsBeginImageContextWithOptions(sz, YES, 0.0);
} else {
UIGraphicsBeginImageContext(sz);
}
UIRectClip(clipRect);
[image drawInRect:clipRect];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
To crop only selected portion of image
Please check this link
I have an UIImageView (red squares) that will display a UIImage that must be scaled (I can receive images greater or smaller that the UIImageView). After scaling it, the showed part of the UIImage is the center of it.
What I need is to show the part of the image in the blue squares, how can I archive it?
I'm only able to get the image size (height and width), but it display the original size, when it's supposed to be the scaled one.
self.viewIm = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 120, 80)];
self.viewIm.backgroundColor = [UIColor greenColor];
self.viewIm.layer.borderColor = [UIColor redColor].CGColor;
self.viewIm.layer.borderWidth = 5.0;
UIImage *im = [UIImage imageNamed:#"benjen"];
self.viewIm.image = im;
self.viewIm.contentMode = UIViewContentModeScaleAspectFill;
// self.viewim.clipsToBounds = YES;
[self.view addSubview:self.viewIm];
To do what you're trying to do, I'd recommend looking into CALayer's contentsRect property.
Since seeing your answer, I've been trying to work out the proper solution for a while, but the mathematics escapes me because contentsRect:'s x and y parameters seem sort of mysterious... But here's some code that may point you in the right direction...
float imageAspect = self.imageView.image.size.width/self.imageView.image.size.height;
float imageViewAspect = self.imageView.frame.size.width/self.imageView.frame.size.height;
if (imageAspect > imageViewAspect) {
float scaledImageWidth = self.imageView.frame.size.height * imageAspect;
float offsetWidth = -((scaledImageWidth-self.imageView.frame.size.width)/2);
self.imageView.layer.contentsRect = CGRectMake(offsetWidth/self.imageView.frame.size.width, 0.0, 1.0, 1.0);
} else if (imageAspect < imageViewAspect) {
float scaledImageHeight = self.imageView.frame.size.width * imageAspect;
float offsetHeight = ((scaledImageHeight-self.imageView.frame.size.height)/2);
self.imageView.layer.contentsRect = CGRectMake(0.0, offsetHeight/self.imageView.frame.size.height, 1.0, 1.0);
}
Try something like this:
CGRect cropRect = CGRectMake(0,0,200,200);
CGImageRef imageRef = CGImageCreateWithImageInRect([ImageToCrop CGImage],cropRect);
UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
I found a very good approximation on this answer. In that, the category resize the image, and use the center point to crop after that. I adapt it to crop using (0,0) as origin point. As I don't really need a category, I use it as a single method.
- (UIImage *)imageByScalingAndCropping:(UIImage *)image forSize:(CGSize)targetSize {
UIImage *sourceImage = image;
UIImage *newImage = nil;
CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetSize.width;
CGFloat scaledHeight = targetSize.height;
if (CGSizeEqualToSize(image.size, targetSize) == NO) {
if ((targetSize.width / image.size.width) > (targetSize.height / image.size.height)) {
scaleFactor = targetSize.width / image.size.width; // scale to fit height
} else {
scaleFactor = targetSize.height / image.size.height; // scale to fit width
}
scaledWidth = image.size.width * scaleFactor;
scaledHeight = image.size.height * scaleFactor;
}
UIGraphicsBeginImageContext(targetSize); // this will crop
CGRect thumbnailRect = CGRectZero;
thumbnailRect.origin = CGPointZero;
thumbnailRect.size.width = scaledWidth;
thumbnailRect.size.height = scaledHeight;
[sourceImage drawInRect:thumbnailRect];
newImage = UIGraphicsGetImageFromCurrentImageContext();
if(newImage == nil) {
NSLog(#"could not scale image");
}
//pop the context to get back to the default
UIGraphicsEndImageContext();
return newImage;
}
And my call is something like this:
self.viewIm = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 120, 80)];
self.viewIm.image = [self imageByScalingAndCropping:[UIImage imageNamed:#"benjen"] forSize:CGSizeMake(120, 80)];
[self.view addSubview:self.viewIm];
I've spent some time on this and finally created a Swift 3.2 solution (based on one of my answers on another thread, as well as one of the answers above). This code only allows for Y translation of the image, but with some tweaks anyone should be able to add horizontal translation as well ;)
let yOffset: CGFloat = 20
myImageView.contentMode = .scaleAspectFill
//scale image to fit the imageView's width (maintaining aspect ratio), but allow control over the image's Y position
UIGraphicsBeginImageContextWithOptions(myImageView.frame.size, myImageView.isOpaque, 0.0)
let ratio = myImage.size.width / myImage.size.height
let newHeight = myImageView.frame.width / ratio
let rect = CGRect(x: 0, y: -yOffset, width: myImageView.frame.width, height: newHeight)
myImage.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext() ?? myImage
UIGraphicsEndImageContext()
//set the new image
myImageView.image = newImage
Now you can adjust how far down or up you need the image to be by changing the yOffset.
When a image is cropped from the center then crop image will take the aspect ratio of source image,But According to my requirement, aspect ratio will be change with new crop size.
I want to get exact center part of image with new aspect ratio.For example a large image is of size (320*480) then I want to crop center part of image of size (100,100) and aspect ratio will also be 100*100 ,No outer white or black part is required and image quality will be high.
Cropping function :
- (UIImage *)cropImage:(UIImage*)image andFrame:(CGRect)rect {
//Note : rec is nothing but the image frame which u want to crop exactly.
rect = CGRectMake(rect.origin.x*image.scale,
rect.origin.y*image.scale,
rect.size.width*image.scale,
rect.size.height*image.scale);
CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], rect);
UIImage *result = [UIImage imageWithCGImage:imageRef
scale:image.scale
orientation:image.imageOrientation];
CGImageRelease(imageRef);
return result;
}
Please help me.
This might run
- (UIImage *)imageByCroppingImage:(UIImage *)image toSize:(CGSize)size
{
// not equivalent to image.size (which depends on the imageOrientation)!
double refWidth = CGImageGetWidth(image.CGImage);
double refHeight = CGImageGetHeight(image.CGImage);
double x = (refWidth - size.width) / 2.0;
double y = (refHeight - size.height) / 2.0;
CGRect cropRect = CGRectMake(x, y, size.height, size.width);
CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], cropRect);
UIImage *cropped = [UIImage imageWithCGImage:imageRef scale:0.0 orientation:self.imageOrientation];
CGImageRelease(imageRef);
return cropped;
}
var imageView = UIImageView()
// height and width values corresponds to rectangle height and width
imageView = UIImageView(frame: CGRectMake(0, 0, width, height ))
imageView.image = UIImage(named: "Your Image Name")
// by setting content mode to .ScaleAspectFill image centrally fill in imageView. image might appear beyond image frame.
imageView.contentMode = .ScaleAspectFill
// by setting .clipsToBouds to true, image set to image frame.
imageView.clipsToBounds = true
view.addSubview(imageView)
// Work this code
-(UIImage *)croppedImage
{
UIImage *myImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.bezierPath closePath];
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0.0, 0.0, 0.0, 0.0);
_b_image = self.bg_imageview.image;
CGSize imageSize = _b_image.size;
CGRect imageRect = CGRectMake(0, 0, imageSize.width, imageSize.height);
UIGraphicsBeginImageContextWithOptions(imageSize, NO, [[UIScreen mainScreen] scale]);
[self.bezierPath addClip];
[_b_image drawInRect:imageRect];
UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return croppedImage;
}
Calculate crop rect from image
float imgHeight = 100.0f; //Any according to requirement
float imgWidth = 100.0f; //Any according to requirement
CGRect cropRect = CGRectMake((largeImage.size.width/2)-(imgWidth/2),largeImage.size.height/2)-(imgHeight/2),imgWidth,imgHeight);
Now crop it
CGImageRef imageRef = CGImageCreateWithImageInRect([largeImage CGImage], cropRect);
// or use the UIImage wherever you like
UIImage *croppedImg = [UIImage imageWithCGImage:imageRef]];
CGImageRelease(imageRef);
How may I retrieve the image from imageView sized as it is displayed (given the content mode), and not as it is according to native properties?
Code:
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, WID, WID)];
imageView.center = CGPointMake(point.x, point.y + Y_OFFSET);
imageView.image = [UIImage imageNamed:#"img"];
imageView.contentMode = UIViewContentModeScaleAspectFit;
You have to draw the image again then save it.
// Image frame size
CGSize size = imageView.bounds.size;
// Grab a new CGContext
UIGraphicsBeginImageContextWithOptions(size, false, 0.0);
// Draw the image
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
// Grab the new image
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The above code draws the image in the frame, stretched to the bounds. If you want any other modes of how it is drawn, you have to calculate them yourself and put the desired stuff in the "Draw the image" line of code.
For example, for aspect fit, check out this algorithm:
- (CGRect) aspectFittedRect:(CGSize)inSize max:(CGRect)maxRect {
float originalAspectRatio = inSize.width / inSize.height;
float maxAspectRatio = maxRect.size.width / maxRect.size.height;
CGRect newRect = maxRect;
if (originalAspectRatio > maxAspectRatio) { // scale by width
newRect.size.height = maxRect.size.height * inSize.height / inSize.width;
newRect.origin.y += (maxRect.size.height - newRect.size.height)/2.0;
} else {
newRect.size.width = maxRect.size.height * inSize.width / inSize.height;
newRect.origin.x += (maxRect.size.width - newRect.size.width)/2.0;
}
return CGRectIntegral(newRect);
}
Just pass in imageView.image.size as inSize and imageView.bounds as maxRect.
Source:
http://iphonedevsdk.com/forum/iphone-sdk-development-advanced-discussion/15001-aspect-fit-algorithm.html
How can I crop my imageView into a bubble shape which I have the image in my project.
Following is simple code for resizing or crop image, you need to just pass height or width for image as you want:
For Get Croped Image:
UIImage *croppedImg = nil;
CGRect cropRect = CGRectMake(AS YOu Need);
croppedImg = [self croppIngimageByImageName:self.imageView.image toRect:cropRect];
Use following method that return UIImage (as You want size of image)
- (UIImage *)croppIngimageByImageName:(UIImage *)imageToCrop toRect:(CGRect)rect
{
//CGRect CropRect = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height+15);
CGImageRef imageRef = CGImageCreateWithImageInRect([imageToCrop CGImage], rect);
UIImage *cropped = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
return cropped;
}
Here you get Croped Image that return by above method;
OR RESIZING
And also Use following method for specific hight and width with image for Resizing UIImage:
+ (UIImage*)resizeImage:(UIImage*)image withWidth:(int)width withHeight:(int)height
{
CGSize newSize = CGSizeMake(width, height);
float widthRatio = newSize.width/image.size.width;
float heightRatio = newSize.height/image.size.height;
if(widthRatio > heightRatio)
{
newSize=CGSizeMake(image.size.width*heightRatio,image.size.height*heightRatio);
}
else
{
newSize=CGSizeMake(image.size.width*widthRatio,image.size.height*widthRatio);
}
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
This method return NewImage, with specific size that you want.