I've got some code that resizes an image so I can get a scaled chunk of the center of the image - I use this to take a UIImage
and return a small, square representation of an image, similar to what's seen in the album view of the Photos app. (I know I could use a UIImageView
and adjust the crop mode to achieve the same results, but these images are sometimes displayed in UIWebViews
).
I've started to notice some crashes in this code and I'm a bit stumped. I've got two different theories and I'm wondering if either is on-base.
Theory 1) I achieve the cropping by drawing into an offscreen image context of my target size. Since I want the center portion of the image, I set the CGRect
argument passed to drawInRect
to something that's larger than the bounds of my image context. I was hoping this was Kosher, but am I instead attempting to draw over other memory that I shouldn't be touching?
Theory 2) I'm doing all of this in a background thread. I know there are portions of UIKit that are restricted to the main thread. I was assuming / hoping that drawing to an offscreen view wasn't one of these. Am I wrong?
(Oh, how I miss NSImage's drawInRect:fromRect:operation:fraction:
method.)
This question is related to
ios
objective-c
cocoa-touch
uiimagepickercontroller
image-manipulation
Here's an updated Swift 3 version based on Noodles answer
func cropping(to rect: CGRect) -> UIImage? {
if let cgCrop = cgImage?.cropping(to: rect) {
return UIImage(cgImage: cgCrop)
}
else if let ciCrop = ciImage?.cropping(to: rect) {
return UIImage(ciImage: ciCrop)
}
return nil
}
Below code snippet might help.
import UIKit
extension UIImage {
func cropImage(toRect rect: CGRect) -> UIImage? {
if let imageRef = self.cgImage?.cropping(to: rect) {
return UIImage(cgImage: imageRef)
}
return nil
}
}
Looks a little bit strange but works great and takes into consideration image orientation:
var image:UIImage = ...
let img = CIImage(image: image)!.imageByCroppingToRect(rect)
image = UIImage(CIImage: img, scale: 1, orientation: image.imageOrientation)
I wasn't satisfied with other solutions because they either draw several time (using more power than necessary) or have problems with orientation. Here is what I used for a scaled square croppedImage from a UIImage * image.
CGFloat minimumSide = fminf(image.size.width, image.size.height);
CGFloat finalSquareSize = 600.;
//create new drawing context for right size
CGRect rect = CGRectMake(0, 0, finalSquareSize, finalSquareSize);
CGFloat scalingRatio = 640.0/minimumSide;
UIGraphicsBeginImageContext(rect.size);
//draw
[image drawInRect:CGRectMake((minimumSide - photo.size.width)*scalingRatio/2., (minimumSide - photo.size.height)*scalingRatio/2., photo.size.width*scalingRatio, photo.size.height*scalingRatio)];
UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
To crop retina images while keeping the same scale and orientation, use the following method in a UIImage category (iOS 4.0 and above):
- (UIImage *)crop:(CGRect)rect {
if (self.scale > 1.0f) {
rect = CGRectMake(rect.origin.x * self.scale,
rect.origin.y * self.scale,
rect.size.width * self.scale,
rect.size.height * self.scale);
}
CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect);
UIImage *result = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
CGImageRelease(imageRef);
return result;
}
Look at https://github.com/vvbogdan/BVCropPhoto
- (UIImage *)croppedImage { CGFloat scale = self.sourceImage.size.width / self.scrollView.contentSize.width; UIImage *finalImage = nil; CGRect targetFrame = CGRectMake((self.scrollView.contentInset.left + self.scrollView.contentOffset.x) * scale, (self.scrollView.contentInset.top + self.scrollView.contentOffset.y) * scale, self.cropSize.width * scale, self.cropSize.height * scale); CGImageRef contextImage = CGImageCreateWithImageInRect([[self imageWithRotation:self.sourceImage] CGImage], targetFrame); if (contextImage != NULL) { finalImage = [UIImage imageWithCGImage:contextImage scale:self.sourceImage.scale orientation:UIImageOrientationUp]; CGImageRelease(contextImage); } return finalImage; } - (UIImage *)imageWithRotation:(UIImage *)image { if (image.imageOrientation == UIImageOrientationUp) return image; CGAffineTransform transform = CGAffineTransformIdentity; switch (image.imageOrientation) { case UIImageOrientationDown: case UIImageOrientationDownMirrored: transform = CGAffineTransformTranslate(transform, image.size.width, image.size.height); transform = CGAffineTransformRotate(transform, M_PI); break; case UIImageOrientationLeft: case UIImageOrientationLeftMirrored: transform = CGAffineTransformTranslate(transform, image.size.width, 0); transform = CGAffineTransformRotate(transform, M_PI_2); break; case UIImageOrientationRight: case UIImageOrientationRightMirrored: transform = CGAffineTransformTranslate(transform, 0, image.size.height); transform = CGAffineTransformRotate(transform, -M_PI_2); break; case UIImageOrientationUp: case UIImageOrientationUpMirrored: break; } switch (image.imageOrientation) { case UIImageOrientationUpMirrored: case UIImageOrientationDownMirrored: transform = CGAffineTransformTranslate(transform, image.size.width, 0); transform = CGAffineTransformScale(transform, -1, 1); break; case UIImageOrientationLeftMirrored: case UIImageOrientationRightMirrored: transform = CGAffineTransformTranslate(transform, image.size.height, 0); transform = CGAffineTransformScale(transform, -1, 1); break; case UIImageOrientationUp: case UIImageOrientationDown: case UIImageOrientationLeft: case UIImageOrientationRight: break; } // Now we draw the underlying CGImage into a new context, applying the transform // calculated above. CGContextRef ctx = CGBitmapContextCreate(NULL, image.size.width, image.size.height, CGImageGetBitsPerComponent(image.CGImage), 0, CGImageGetColorSpace(image.CGImage), CGImageGetBitmapInfo(image.CGImage)); CGContextConcatCTM(ctx, transform); switch (image.imageOrientation) { case UIImageOrientationLeft: case UIImageOrientationLeftMirrored: case UIImageOrientationRight: case UIImageOrientationRightMirrored: // Grr... CGContextDrawImage(ctx, CGRectMake(0, 0, image.size.height, image.size.width), image.CGImage); break; default: CGContextDrawImage(ctx, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage); break; } // And now we just create a new UIImage from the drawing context CGImageRef cgimg = CGBitmapContextCreateImage(ctx); UIImage *img = [UIImage imageWithCGImage:cgimg]; CGContextRelease(ctx); CGImageRelease(cgimg); return img; }
You can make a UIImage category and use it wherever you need. Based on HitScans response and comments bellow it.
@implementation UIImage (Crop)
- (UIImage *)crop:(CGRect)rect {
rect = CGRectMake(rect.origin.x*self.scale,
rect.origin.y*self.scale,
rect.size.width*self.scale,
rect.size.height*self.scale);
CGImageRef imageRef = CGImageCreateWithImageInRect([self CGImage], rect);
UIImage *result = [UIImage imageWithCGImage:imageRef
scale:self.scale
orientation:self.imageOrientation];
CGImageRelease(imageRef);
return result;
}
@end
You can use it this way:
UIImage *imageToCrop = <yourImageToCrop>;
CGRect cropRect = <areaYouWantToCrop>;
//for example
//CGRectMake(0, 40, 320, 100);
UIImage *croppedImage = [imageToCrop crop:cropRect];
CGSize size = [originalImage size];
int padding = 20;
int pictureSize = 300;
int startCroppingPosition = 100;
if (size.height > size.width) {
pictureSize = size.width - (2.0 * padding);
startCroppingPosition = (size.height - pictureSize) / 2.0;
} else {
pictureSize = size.height - (2.0 * padding);
startCroppingPosition = (size.width - pictureSize) / 2.0;
}
// WTF: Don't forget that the CGImageCreateWithImageInRect believes that
// the image is 180 rotated, so x and y are inverted, same for height and width.
CGRect cropRect = CGRectMake(startCroppingPosition, padding, pictureSize, pictureSize);
CGImageRef imageRef = CGImageCreateWithImageInRect([originalImage CGImage], cropRect);
UIImage *newImage = [UIImage imageWithCGImage:imageRef scale:1.0 orientation:originalImage.imageOrientation];
[m_photoView setImage:newImage];
CGImageRelease(imageRef);
Most of the responses I've seen only deals with a position of (0, 0) for (x, y). Ok that's one case but I'd like my cropping operation to be centered. What took me a while to figure out is the line following the WTF comment.
Let's take the case of an image captured with a portrait orientation:
Hope it makes sense! If it does not, try different values you'll see that the logic is inverted when it comes to choosing the right x, y, width, and height for your cropRect.
Swift Extension
extension UIImage {
func crop(var rect: CGRect) -> UIImage {
rect.origin.x*=self.scale
rect.origin.y*=self.scale
rect.size.width*=self.scale
rect.size.height*=self.scale
let imageRef = CGImageCreateWithImageInRect(self.CGImage, rect)
let image = UIImage(CGImage: imageRef, scale: self.scale, orientation: self.imageOrientation)!
return image
}
}
(UIImage *)squareImageWithImage:(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;
}
I use the method below.
-(UIImage *)getNeedImageFrom:(UIImage*)image cropRect:(CGRect)rect
{
CGSize cropSize = rect.size;
CGFloat widthScale =
image.size.width/self.imageViewOriginal.bounds.size.width;
CGFloat heightScale =
image.size.height/self.imageViewOriginal.bounds.size.height;
cropSize = CGSizeMake(rect.size.width*widthScale,
rect.size.height*heightScale);
CGPoint pointCrop = CGPointMake(rect.origin.x*widthScale,
rect.origin.y*heightScale);
rect = CGRectMake(pointCrop.x, pointCrop.y, cropSize.width,
cropSize.height);
CGImageRef subImage = CGImageCreateWithImageInRect(image.CGImage, rect);
UIImage *croppedImage = [UIImage imageWithCGImage:subImage];
CGImageRelease(subImage);
return croppedImage;
}
Here is my UIImage crop implementation which obeys the imageOrientation property. All orientations were thoroughly tested.
inline double rad(double deg)
{
return deg / 180.0 * M_PI;
}
UIImage* UIImageCrop(UIImage* img, CGRect rect)
{
CGAffineTransform rectTransform;
switch (img.imageOrientation)
{
case UIImageOrientationLeft:
rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(90)), 0, -img.size.height);
break;
case UIImageOrientationRight:
rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-90)), -img.size.width, 0);
break;
case UIImageOrientationDown:
rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-180)), -img.size.width, -img.size.height);
break;
default:
rectTransform = CGAffineTransformIdentity;
};
rectTransform = CGAffineTransformScale(rectTransform, img.scale, img.scale);
CGImageRef imageRef = CGImageCreateWithImageInRect([img CGImage], CGRectApplyAffineTransform(rect, rectTransform));
UIImage *result = [UIImage imageWithCGImage:imageRef scale:img.scale orientation:img.imageOrientation];
CGImageRelease(imageRef);
return result;
}
func cropImage(imageToCrop:UIImage, toRect rect:CGRect) -> UIImage{
let imageRef:CGImage = imageToCrop.cgImage!.cropping(to: rect)!
let cropped:UIImage = UIImage(cgImage:imageRef)
return cropped
}
let imageTop:UIImage = UIImage(named:"one.jpg")! // add validation
with help of this bridge function CGRectMake
-> CGRect
(credits to this answer answered by @rob mayoff
):
func CGRectMake(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) -> CGRect {
return CGRect(x: x, y: y, width: width, height: height)
}
The usage is:
if var image:UIImage = UIImage(named:"one.jpg"){
let croppedImage = cropImage(imageToCrop: image, toRect: CGRectMake(
image.size.width/4,
0,
image.size.width/2,
image.size.height)
)
}
Output:
- (UIImage *)getSubImage:(CGRect) rect{
CGImageRef subImageRef = CGImageCreateWithImageInRect(self.CGImage, rect);
CGRect smallBounds = CGRectMake(rect.origin.x, rect.origin.y, CGImageGetWidth(subImageRef), CGImageGetHeight(subImageRef));
UIGraphicsBeginImageContext(smallBounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, smallBounds, subImageRef);
UIImage* smallImg = [UIImage imageWithCGImage:subImageRef];
UIGraphicsEndImageContext();
return smallImg;
}
None of the answers here handle all of the scale and rotation issues 100% correctly. Here's a synthesis of everything said so far, up-to-date as of iOS7/8. It's meant to be included as a method in a category on UIImage.
- (UIImage *)croppedImageInRect:(CGRect)rect
{
double (^rad)(double) = ^(double deg) {
return deg / 180.0 * M_PI;
};
CGAffineTransform rectTransform;
switch (self.imageOrientation) {
case UIImageOrientationLeft:
rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(90)), 0, -self.size.height);
break;
case UIImageOrientationRight:
rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-90)), -self.size.width, 0);
break;
case UIImageOrientationDown:
rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-180)), -self.size.width, -self.size.height);
break;
default:
rectTransform = CGAffineTransformIdentity;
};
rectTransform = CGAffineTransformScale(rectTransform, self.scale, self.scale);
CGImageRef imageRef = CGImageCreateWithImageInRect([self CGImage], CGRectApplyAffineTransform(rect, rectTransform));
UIImage *result = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
CGImageRelease(imageRef);
return result;
}
On iOS9.2SDK ,I use below method to convert frame from UIView to UIImage
-(UIImage *)getNeedImageFrom:(UIImage*)image cropRect:(CGRect)rect
{
CGSize cropSize = rect.size;
CGFloat widthScale = image.size.width/self.imageViewOriginal.bounds.size.width;
CGFloat heightScale = image.size.height/self.imageViewOriginal.bounds.size.height;
cropSize = CGSizeMake(rect.size.width*widthScale,
rect.size.height*heightScale);
CGPoint pointCrop = CGPointMake(rect.origin.x*widthScale,
rect.origin.y*heightScale);
rect = CGRectMake(pointCrop.x, pointCrop.y, cropSize.width, cropSize.height);
CGImageRef subImage = CGImageCreateWithImageInRect(image.CGImage, rect);
UIImage *croppedImage = [UIImage imageWithCGImage:subImage];
CGImageRelease(subImage);
return croppedImage;
}
Follow Answer of @Arne. I Just fixing to Category function. put it in Category of UIImage.
-(UIImage*)cropImage:(CGRect)rect{
UIGraphicsBeginImageContextWithOptions(rect.size, false, [self scale]);
[self drawAtPoint:CGPointMake(-rect.origin.x, -rect.origin.y)];
UIImage* cropped_image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return cropped_image;
}
Update 2014-05-28: I wrote this when iOS 3 or so was the hot new thing, I'm certain there are better ways to do this by now, possibly built-in. As many people have mentioned, this method doesn't take rotation into account; read some additional answers and spread some upvote love around to keep the responses to this question helpful for everyone.
Original response:
I'm going to copy/paste my response to the same question elsewhere:
There isn't a simple class method to do this, but there is a function that you can use to get the desired results: CGImageCreateWithImageInRect(CGImageRef, CGRect)
will help you out.
Here's a short example using it:
CGImageRef imageRef = CGImageCreateWithImageInRect([largeImage CGImage], cropRect);
// or use the UIImage wherever you like
[UIImageView setImage:[UIImage imageWithCGImage:imageRef]];
CGImageRelease(imageRef);
Swift version of awolf
's answer, which worked for me:
public extension UIImage {
func croppedImage(inRect rect: CGRect) -> UIImage {
let rad: (Double) -> CGFloat = { deg in
return CGFloat(deg / 180.0 * .pi)
}
var rectTransform: CGAffineTransform
switch imageOrientation {
case .left:
let rotation = CGAffineTransform(rotationAngle: rad(90))
rectTransform = rotation.translatedBy(x: 0, y: -size.height)
case .right:
let rotation = CGAffineTransform(rotationAngle: rad(-90))
rectTransform = rotation.translatedBy(x: -size.width, y: 0)
case .down:
let rotation = CGAffineTransform(rotationAngle: rad(-180))
rectTransform = rotation.translatedBy(x: -size.width, y: -size.height)
default:
rectTransform = .identity
}
rectTransform = rectTransform.scaledBy(x: scale, y: scale)
let transformedRect = rect.applying(rectTransform)
let imageRef = cgImage!.cropping(to: transformedRect)!
let result = UIImage(cgImage: imageRef, scale: scale, orientation: imageOrientation)
return result
}
}
Swift 5:
extension UIImage {
func cropped(rect: CGRect) -> UIImage? {
guard let cgImage = cgImage else { return nil }
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0)
let context = UIGraphicsGetCurrentContext()
context?.translateBy(x: 0.0, y: self.size.height)
context?.scaleBy(x: 1.0, y: -1.0)
context?.draw(cgImage, in: CGRect(x: rect.minX, y: rect.minY, width: self.size.width, height: self.size.height), byTiling: false)
let croppedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return croppedImage
}
}
extension UIImage {
func crop(rect: CGRect) -> UIImage? {
var scaledRect = rect
scaledRect.origin.x *= scale
scaledRect.origin.y *= scale
scaledRect.size.width *= scale
scaledRect.size.height *= scale
guard let imageRef: CGImage = cgImage?.cropping(to: scaledRect) else {
return nil
}
return UIImage(cgImage: imageRef, scale: scale, orientation: imageOrientation)
}
}
Best solution for cropping an UIImage in Swift, in term of precision, pixels scaling ...:
private func squareCropImageToSideLength(let sourceImage: UIImage,
let sideLength: CGFloat) -> UIImage {
// input size comes from image
let inputSize: CGSize = sourceImage.size
// round up side length to avoid fractional output size
let sideLength: CGFloat = ceil(sideLength)
// output size has sideLength for both dimensions
let outputSize: CGSize = CGSizeMake(sideLength, sideLength)
// calculate scale so that smaller dimension fits sideLength
let scale: CGFloat = max(sideLength / inputSize.width,
sideLength / inputSize.height)
// scaling the image with this scale results in this output size
let scaledInputSize: CGSize = CGSizeMake(inputSize.width * scale,
inputSize.height * scale)
// determine point in center of "canvas"
let center: CGPoint = CGPointMake(outputSize.width/2.0,
outputSize.height/2.0)
// calculate drawing rect relative to output Size
let outputRect: CGRect = CGRectMake(center.x - scaledInputSize.width/2.0,
center.y - scaledInputSize.height/2.0,
scaledInputSize.width,
scaledInputSize.height)
// begin a new bitmap context, scale 0 takes display scale
UIGraphicsBeginImageContextWithOptions(outputSize, true, 0)
// optional: set the interpolation quality.
// For this you need to grab the underlying CGContext
let ctx: CGContextRef = UIGraphicsGetCurrentContext()
CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh)
// draw the source image into the calculated rect
sourceImage.drawInRect(outputRect)
// create new image from bitmap context
let outImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()
// clean up
UIGraphicsEndImageContext()
// pass back new image
return outImage
}
Instructions used to call this function:
let image: UIImage = UIImage(named: "Image.jpg")!
let squareImage: UIImage = self.squareCropImageToSideLength(image, sideLength: 320)
self.myUIImageView.image = squareImage
Note: the initial source code inspiration written in Objective-C has been found on "Cocoanetics" blog.
Swift 2.0 Update (CIImage
compatibility)
Expanding off of Maxim's Answer but works if your image is CIImage
based, as well.
public extension UIImage {
func imageByCroppingToRect(rect: CGRect) -> UIImage? {
if let image = CGImageCreateWithImageInRect(self.CGImage, rect) {
return UIImage(CGImage: image)
} else if let image = (self.CIImage)?.imageByCroppingToRect(rect) {
return UIImage(CIImage: image)
}
return nil
}
}
Heads up: all these answers assume a CGImage
-backed image object.
image.CGImage
can return nil, if the UIImage
is backed by a CIImage
, which would be the case if you created this image using a CIFilter
.
In that case, you might have to draw the image in a new context, and return that image (slow).
UIImage* crop(UIImage *image, rect) {
UIGraphicsBeginImageContextWithOptions(rect.size, false, [image scale]);
[image drawAtPoint:CGPointMake(-rect.origin.x, -rect.origin.y)];
cropped_image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return cropped_image;
}
Source: Stackoverflow.com