Cropping image with Swift and put it on center position

IosSwift

Ios 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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjhayviView Question on Stackoverflow
Solution 1 - IosColeView Answer on Stackoverflow
Solution 2 - IosTanel TeemuskView Answer on Stackoverflow
Solution 3 - IosKishor PahalwaniView Answer on Stackoverflow
Solution 4 - IosJack GuoView Answer on Stackoverflow
Solution 5 - IosBrandon AView Answer on Stackoverflow
Solution 6 - IosBilalReffasView Answer on Stackoverflow
Solution 7 - IosRamesh SainView Answer on Stackoverflow
Solution 8 - IosmadxView Answer on Stackoverflow
Solution 9 - IosdeafmutemagicView Answer on Stackoverflow
Solution 10 - IosPADView Answer on Stackoverflow
Solution 11 - IosKamil HarasimowiczView Answer on Stackoverflow
Solution 12 - IosiPeraView Answer on Stackoverflow
Solution 13 - IosFujiaView Answer on Stackoverflow
Solution 14 - IosStephane Darcy SIMO MBAView Answer on Stackoverflow
Solution 15 - IosSMarxView Answer on Stackoverflow