Cropping image with Swift and put it on center position
IosSwiftIos Problem Overview
In Swift programming , how do you crop an image and put it on the center afterwards?
This is what I've got so far ... I've successfully crop the image but I want to put it on the center after
ImgView.image = OrigImage
var masklayer = CAShapeLayer()
masklayer.frame = ImgView.frame
masklayer.path = path.CGPath
masklayer.fillColor = UIColor.whiteColor().CGColor
masklayer.backgroundColor = UIColor.clearColor().CGColor
ImgView.layer.mask = masklayer
UIGraphicsBeginImageContext(ImgView.bounds.size);
ImgView.layer.renderInContext(UIGraphicsGetCurrentContext())
var image = UIGraphicsGetImageFromCurrentImageContext()
ImgView.image = image
UIGraphicsEndImageContext();
UPDATE :
let rect: CGRect = CGRectMake(path.bounds.minX, path.bounds.minY, path.bounds.width, path.bounds.height)
// Create bitmap image from context using the rect
let imageRef: CGImageRef = CGImageCreateWithImageInRect(image.CGImage, rect)
ImgView.bounds = rect
ImgView.image = UIImage(CGImage: imageRef)
I was able to center it by getting the path.bound and size and change the bounds of my ImageView. :)
Ios Solutions
Solution 1 - Ios
To get a centered position for your crop, you can halve the difference of the height and width. Then you can assign the bounds for the new width and height after checking the orientation of the image (which part is longer)
func cropToBounds(image: UIImage, width: Double, height: Double) -> UIImage {
let contextImage: UIImage = UIImage(CGImage: image.CGImage)!
let contextSize: CGSize = contextImage.size
var posX: CGFloat = 0.0
var posY: CGFloat = 0.0
var cgwidth: CGFloat = CGFloat(width)
var cgheight: CGFloat = CGFloat(height)
// See what size is longer and create the center off of that
if contextSize.width > contextSize.height {
posX = ((contextSize.width - contextSize.height) / 2)
posY = 0
cgwidth = contextSize.height
cgheight = contextSize.height
} else {
posX = 0
posY = ((contextSize.height - contextSize.width) / 2)
cgwidth = contextSize.width
cgheight = contextSize.width
}
let rect: CGRect = CGRectMake(posX, posY, cgwidth, cgheight)
// Create bitmap image from context using the rect
let imageRef: CGImageRef = CGImageCreateWithImageInRect(contextImage.CGImage, rect)
// Create a new image based on the imageRef and rotate back to the original orientation
let image: UIImage = UIImage(CGImage: imageRef, scale: image.scale, orientation: image.imageOrientation)!
return image
}
I found most of this info over at this website in case you wanted to read further.
Updated for Swift 4
func cropToBounds(image: UIImage, width: Double, height: Double) -> UIImage {
let cgimage = image.cgImage!
let contextImage: UIImage = UIImage(cgImage: cgimage)
let contextSize: CGSize = contextImage.size
var posX: CGFloat = 0.0
var posY: CGFloat = 0.0
var cgwidth: CGFloat = CGFloat(width)
var cgheight: CGFloat = CGFloat(height)
// See what size is longer and create the center off of that
if contextSize.width > contextSize.height {
posX = ((contextSize.width - contextSize.height) / 2)
posY = 0
cgwidth = contextSize.height
cgheight = contextSize.height
} else {
posX = 0
posY = ((contextSize.height - contextSize.width) / 2)
cgwidth = contextSize.width
cgheight = contextSize.width
}
let rect: CGRect = CGRect(x: posX, y: posY, width: cgwidth, height: cgheight)
// Create bitmap image from context using the rect
let imageRef: CGImage = cgimage.cropping(to: rect)!
// Create a new image based on the imageRef and rotate back to the original orientation
let image: UIImage = UIImage(cgImage: imageRef, scale: image.scale, orientation: image.imageOrientation)
return image
}
Solution 2 - Ios
The accepted answer only does squares for me. I needed a bit more flexible cropping mechanism so I wrote an extension as follows:
> import UIKit
extension UIImage {
func crop(to:CGSize) -> UIImage {
guard let cgimage = self.cgImage else { return self }
let contextImage: UIImage = UIImage(cgImage: cgimage)
guard let newCgImage = contextImage.cgImage else { return self }
let contextSize: CGSize = contextImage.size
//Set to square
var posX: CGFloat = 0.0
var posY: CGFloat = 0.0
let cropAspect: CGFloat = to.width / to.height
var cropWidth: CGFloat = to.width
var cropHeight: CGFloat = to.height
if to.width > to.height { //Landscape
cropWidth = contextSize.width
cropHeight = contextSize.width / cropAspect
posY = (contextSize.height - cropHeight) / 2
} else if to.width < to.height { //Portrait
cropHeight = contextSize.height
cropWidth = contextSize.height * cropAspect
posX = (contextSize.width - cropWidth) / 2
} else { //Square
if contextSize.width >= contextSize.height { //Square on landscape (or square)
cropHeight = contextSize.height
cropWidth = contextSize.height * cropAspect
posX = (contextSize.width - cropWidth) / 2
}else{ //Square on portrait
cropWidth = contextSize.width
cropHeight = contextSize.width / cropAspect
posY = (contextSize.height - cropHeight) / 2
}
}
let rect: CGRect = CGRect(x: posX, y: posY, width: cropWidth, height: cropHeight)
// Create bitmap image from context using the rect
guard let imageRef: CGImage = newCgImage.cropping(to: rect) else { return self}
// Create a new image based on the imageRef and rotate back to the original orientation
let cropped: UIImage = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)
UIGraphicsBeginImageContextWithOptions(to, false, self.scale)
cropped.draw(in: CGRect(x: 0, y: 0, width: to.width, height: to.height))
let resized = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resized ?? self
}
}
You can use it so:
let size = CGSize(width: 300, height: 200)
let image = UIImage(named: "my_great_photo")?.crop(size)
If anyone has ideas how to make the landscape, portrait and square handling a bit better let me know.
Solution 3 - Ios
You can try this answer. It is written in swift 3.
extension UIImage {
func crop(to:CGSize) -> UIImage {
guard let cgimage = self.cgImage else { return self }
let contextImage: UIImage = UIImage(cgImage: cgimage)
let contextSize: CGSize = contextImage.size
//Set to square
var posX: CGFloat = 0.0
var posY: CGFloat = 0.0
let cropAspect: CGFloat = to.width / to.height
var cropWidth: CGFloat = to.width
var cropHeight: CGFloat = to.height
if to.width > to.height { //Landscape
cropWidth = contextSize.width
cropHeight = contextSize.width / cropAspect
posY = (contextSize.height - cropHeight) / 2
} else if to.width < to.height { //Portrait
cropHeight = contextSize.height
cropWidth = contextSize.height * cropAspect
posX = (contextSize.width - cropWidth) / 2
} else { //Square
if contextSize.width >= contextSize.height { //Square on landscape (or square)
cropHeight = contextSize.height
cropWidth = contextSize.height * cropAspect
posX = (contextSize.width - cropWidth) / 2
}else{ //Square on portrait
cropWidth = contextSize.width
cropHeight = contextSize.width / cropAspect
posY = (contextSize.height - cropHeight) / 2
}
}
let rect: CGRect = CGRect(x : posX, y : posY, width : cropWidth, height : cropHeight)
// Create bitmap image from context using the rect
let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!
// Create a new image based on the imageRef and rotate back to the original orientation
let cropped: UIImage = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)
cropped.draw(in: CGRect(x : 0, y : 0, width : to.width, height : to.height))
return cropped
}
}
Solution 4 - Ios
This is THE answer, credit to @awolf (https://stackoverflow.com/questions/158914/cropping-an-uiimage/18602671#18602671). Handles scale and orientation perfectly. Just call this method on the image you want to crop, and pass in the cropping CGRect
without worrying about scale or orientation. Feel free to check whether cgImage
is nil instead of force unwrapping it like I did here.
extension UIImage {
func croppedInRect(rect: CGRect) -> UIImage {
func rad(_ degree: Double) -> CGFloat {
return CGFloat(degree / 180.0 * .pi)
}
var rectTransform: CGAffineTransform
switch imageOrientation {
case .left:
rectTransform = CGAffineTransform(rotationAngle: rad(90)).translatedBy(x: 0, y: -self.size.height)
case .right:
rectTransform = CGAffineTransform(rotationAngle: rad(-90)).translatedBy(x: -self.size.width, y: 0)
case .down:
rectTransform = CGAffineTransform(rotationAngle: rad(-180)).translatedBy(x: -self.size.width, y: -self.size.height)
default:
rectTransform = .identity
}
rectTransform = rectTransform.scaledBy(x: self.scale, y: self.scale)
let imageRef = self.cgImage!.cropping(to: rect.applying(rectTransform))
let result = UIImage(cgImage: imageRef!, scale: self.scale, orientation: self.imageOrientation)
return result
}
}
If you want the cropping rect to be centered, just do simple math. Along the lines of
let x = (image.width - croppingFrame.width) / 2
Another note: if you are working with imageView
embedded in a scrollView
, there is one additional step, you have to take the zoom factor into account. Assuming your imageView
spans the entire content view of the scrollView
, and you use the bounds of the scrollView
as the cropping frame, the cropped image can be obtained as
let ratio = imageView.image!.size.height / scrollView.contentSize.height
let origin = CGPoint(x: scrollView.contentOffset.x * ratio, y: scrollView.contentOffset.y * ratio)
let size = CGSize(width: scrollView.bounds.size.width * ratio, let height: scrollView.bounds.size.height * ratio)
let cropFrame = CGRect(origin: origin, size: size)
let croppedImage = imageView.image!.croppedInRect(rect: cropFrame)
Solution 5 - Ios
Props to Cole
Swift 3
func crop(image: UIImage, withWidth width: Double, andHeight height: Double) -> UIImage? {
if let cgImage = image.cgImage {
let contextImage: UIImage = UIImage(cgImage: cgImage)
let contextSize: CGSize = contextImage.size
var posX: CGFloat = 0.0
var posY: CGFloat = 0.0
var cgwidth: CGFloat = CGFloat(width)
var cgheight: CGFloat = CGFloat(height)
// See what size is longer and create the center off of that
if contextSize.width > contextSize.height {
posX = ((contextSize.width - contextSize.height) / 2)
posY = 0
cgwidth = contextSize.height
cgheight = contextSize.height
} else {
posX = 0
posY = ((contextSize.height - contextSize.width) / 2)
cgwidth = contextSize.width
cgheight = contextSize.width
}
let rect: CGRect = CGRect(x: posX, y: posY, width: cgwidth, height: cgheight)
// Create bitmap image from context using the rect
var croppedContextImage: CGImage? = nil
if let contextImage = contextImage.cgImage {
if let croppedImage = contextImage.cropping(to: rect) {
croppedContextImage = croppedImage
}
}
// Create a new image based on the imageRef and rotate back to the original orientation
if let croppedImage:CGImage = croppedContextImage {
let image: UIImage = UIImage(cgImage: croppedImage, scale: image.scale, orientation: image.imageOrientation)
return image
}
}
return nil
}
Solution 6 - Ios
Working Swift 3 example
extension UIImage {
func crop(to:CGSize) -> UIImage {
guard let cgimage = self.cgImage else { return self }
let contextImage: UIImage = UIImage(cgImage: cgimage)
let contextSize: CGSize = contextImage.size
//Set to square
var posX: CGFloat = 0.0
var posY: CGFloat = 0.0
let cropAspect: CGFloat = to.width / to.height
var cropWidth: CGFloat = to.width
var cropHeight: CGFloat = to.height
if to.width > to.height { //Landscape
cropWidth = contextSize.width
cropHeight = contextSize.width / cropAspect
posY = (contextSize.height - cropHeight) / 2
} else if to.width < to.height { //Portrait
cropHeight = contextSize.height
cropWidth = contextSize.height * cropAspect
posX = (contextSize.width - cropWidth) / 2
} else { //Square
if contextSize.width >= contextSize.height { //Square on landscape (or square)
cropHeight = contextSize.height
cropWidth = contextSize.height * cropAspect
posX = (contextSize.width - cropWidth) / 2
}else{ //Square on portrait
cropWidth = contextSize.width
cropHeight = contextSize.width / cropAspect
posY = (contextSize.height - cropHeight) / 2
}
}
let rect: CGRect = CGRect(x: posX, y: posY, width: cropWidth, height: cropHeight)
// Create bitmap image from context using the rect
let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!
// Create a new image based on the imageRef and rotate back to the original orientation
let cropped: UIImage = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)
UIGraphicsBeginImageContextWithOptions(to, true, self.scale)
cropped.draw(in: CGRect(x: 0, y: 0, width: to.width, height: to.height))
let resized = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resized!
}
}
Solution 7 - Ios
You can just crop using:
let croppedImage = yourImage.cgImage.cropping(to:rect)
Solution 8 - Ios
In swift 4.1 I would do simply:
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 20
Credits to Stretching, Redrawing and Positioning with contentMode
Solution 9 - Ios
I came up with a code that will give a desired cropped aspect ratio, regardless of original video frame size (adapted from @Cole's answer):
func cropImage(uncroppedImage: UIImage, cropWidth: CGFloat, cropHeight: CGFloat) -> UIImage {
let contextImage: UIImage = UIImage(cgImage: uncroppedImage.cgImage!)
let contextSize: CGSize = contextImage.size
var cropX: CGFloat = 0.0
var cropY: CGFloat = 0.0
var cropRatio: CGFloat = CGFloat(cropWidth/cropHeight)
var originalRatio: CGFloat = contextSize.width/contextSize.height
var scaledCropHeight: CGFloat = 0.0
var scaledCropWidth: CGFloat = 0.0
// See what size is longer and set crop rect parameters
if originalRatio > cropRatio {
scaledCropHeight = contextSize.height
scaledCropWidth = (contextSize.height/cropHeight) * cropWidth
cropX = (contextSize.width - scaledCropWidth) / 2
cropY = 0
} else {
scaledCropWidth = contextSize.width
scaledCropHeight = (contextSize.width/cropWidth) * cropHeight
cropY = (contextSize.height / scaledCropHeight) / 2
cropX = 0
}
let rect: CGRect = CGRect(x: cropX, y: cropY, width: scaledCropWidth, height: scaledCropHeight)
// Create bitmap image from context using the rect
let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!
// Create a new image based on the imageRef and rotate back to the original orientation
let croppedImage: UIImage = UIImage(cgImage: imageRef, scale: uncroppedImage.scale, orientation: uncroppedImage.imageOrientation)
return croppedImage
}
Hope it helps!
Solution 10 - Ios
You can also, very simply, put the concerned ImageView in "Aspect Fill" mode from the Storyboard, and add this in the code :
imageView.layer.masksToBounds = true
imageView.clipsToBounds = true
Solution 11 - Ios
Accepted answer did not work for me, so I tried wrote my own. Here is an effect of my work:
import UIKit
extension UIImage {
func cropedToRatio(ratio: CGFloat) -> UIImage? {
let newImageWidth = size.height * ratio
let cropRect = CGRect(x: ((size.width - newImageWidth) / 2.0) * scale,
y: 0.0,
width: newImageWidth * scale,
height: size.height * scale)
guard let cgImage = cgImage else {
return nil
}
guard let newCgImage = cgImage.cropping(to: cropRect) else {
return nil
}
return UIImage(cgImage: newCgImage, scale: scale, orientation: imageOrientation)
}
}
This function crop image to given ratio. It keeps image scale. Cropped image is always center of original image.
Solution 12 - Ios
Or make UImage extension
extension UIImage {
func cropped(boundingBox: CGRect) -> UIImage? {
guard let cgImage = self.cgImage?.cropping(to: boundingBox) else {
return nil
}
return UIImage(cgImage: cgImage)
}
}
Solution 13 - Ios
Change this:
masklayer.frame = ImgView.frame
To this:
masklayer.frame = ImgView.bounds
Solution 14 - Ios
you can also use Alamofire and AlamofireImage to crop your image.
https://github.com/Alamofire/AlamofireImage
Installing using CocoaPods
pod 'AlamofireImage'
Usage:
let image = UIImage(named: "unicorn")!
let size = CGSize(width: 100.0, height: 100.0)
// Scale image to size disregarding aspect ratio
let scaledImage = image.af_imageScaled(to: size)
let aspectScaledToFitImage = image.af_imageAspectScaled(toFit: size)
// Scale image to fill specified size while maintaining aspect ratio
let aspectScaledToFillImage = image.af_imageAspectScaled(toFill: size)
Solution 15 - Ios
Swift 5
extension UIImage {
/// A function who takes in a uiimage and crops it to its largest square value
/// - Returns: The cropped image. Nil if the data could not be extracted from the UIImage
internal func croppedToSquare() -> UIImage? {
guard let sourceImageData = self.cgImage else {
return nil
}
let shortestSide = min(self.size.width, self.size.height)
// Determines the x,y coordinate of the top-left corner of the cropped photo
/// The distance from the leading edge of the original photo to the leading edge of the cropped photo (should be 0 for photos in portrait orientation)
let xOffset = (self.size.width - shortestSide) / 2
/// The distance from the top edge of the original photo to the top edge of the cropped photo (should be 0 for photos in landscape orientation)
let yOffset = (self.size.height - shortestSide) / 2
/// A boolean which indicates if the image data is a rotation (reflections don't matter) of the uiimage. If so, they x and y coordinates should be transposed.
let axesAreFlipped = self.imageOrientation == .left || self.imageOrientation == .right
/// The square to crop the image through, with the x, y coordinate describing the top left corner of the square
let cropMask = CGRect(x: axesAreFlipped ? yOffset : xOffset, y: axesAreFlipped ? xOffset : yOffset, width: shortestSide, height: shortestSide).integral
guard let newImageData = sourceImageData.cropping(to: cropMask) else {
return nil
}
return UIImage(cgImage: newImageData, scale: self.imageRendererFormat.scale, orientation: self.imageOrientation)
}
}
This function works by extracting the CGImage data from the given UIImage, then, finding the coordinate of the top left corner of the photo. From this coordinate, a rectangle is drawn with equal sides (the side length being the shortest side of the original image).
Note that the CGImage data describes the data for the original photo wrapped in the UIImage- not what is actually displayed by the UIImage by default. Thus, the original orientation of the image needs to be checked to determine if the x and y values need to be flipped.