55

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. :)

iGatiTech
  • 2,306
  • 1
  • 21
  • 45
jhayvi
  • 569
  • 1
  • 4
  • 5

15 Answers15

83

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
    }
chronikum
  • 716
  • 5
  • 12
Cole
  • 2,641
  • 1
  • 16
  • 34
  • I get some of the code above and used the path.bound on placing it to the center and it work ... (grrrr ... I can't seem to post my code, anyways I'll just update my question above) – jhayvi Aug 17 '15 at 08:16
  • Your solution worked for me. Just wondering if there would be any way to do a cropping animation. – Swift Rabbit Dec 16 '15 at 15:54
  • 3
    Why do you have width/height parameters? – Coder1224 Jan 29 '17 at 06:58
  • 2
    let cgimage = image.cgImage! let contextImage: UIImage = UIImage(cgImage: cgimage) These 2 lines are not necessary. You are converting the UIImage to CGImage and back. – eharo2 May 30 '18 at 18:27
  • let cgimage = image.cgImage! let contextImage: UIImage = UIImage(cgImage: cgimage) These 2 lines are needed to make a contextImage with a scale of 1.0 – Alexey Sobolevsky Sep 28 '19 at 09:53
  • Canthis be used to get 16:9 of an image? – Luke Irvin May 08 '20 at 19:17
29

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.

Mandeep Singh
  • 2,810
  • 1
  • 19
  • 31
Tanel Teemusk
  • 707
  • 8
  • 17
  • This won't work for me. I only get a black box. The variable `cropped` isn't used anywhere. Guess thats the problem here. – aross Sep 01 '16 at 20:17
  • Yup. sorry. had a typo. now the line on the bottom says: cropped.drawInRect(CGRectMake(0, 0, to.width, to.height)) this means cropped image will get resized. I have resizing and cropping methods different, hence it was self.drawInRect before. – Tanel Teemusk Sep 22 '16 at 09:10
  • Wow, thanks. You save me millions of hours. This is so helpful. One question is when cropping the image, the resolution of the image become smaller. Is it possible to keep the image quality while still cropping the image? – nuynait Jan 27 '17 at 19:03
  • Image is coming but cropping area is coming black. How can I change it to white. Thanks – Kishor Pahalwani Apr 24 '17 at 08:53
  • @TanelTeemusk When I capture from camera image rotate automatically. – Ilesh P Aug 26 '17 at 05:04
  • Hey! Great answer. Quick question on PNG images with transparent items around it. It changes it with black background. Any solution? @TanelTeemusk – Yagiz Jan 23 '19 at 00:38
  • Thanks @yagiz for your comment. I have updated the example above to swift 4. Also the transparency for PNG's is preserved now. Let me know if it works. – Tanel Teemusk Jan 24 '19 at 11:02
  • I am using the above extension to crop the image inside a table view cell, and it does nothing at all. – Raja Saad Jun 06 '23 at 07:12
13

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
  }
}
Kishor Pahalwani
  • 1,010
  • 1
  • 23
  • 53
9

This is THE answer, credit to @awolf (Cropping an UIImage). 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)
Jack Guo
  • 3,959
  • 8
  • 39
  • 60
4

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!

deafmutemagic
  • 123
  • 1
  • 7
  • To crop image equally from top and bottom (crop from centerY), please change cropY = (contextSize.height / scaledCropHeight) / 2 to cropY = (contextSize.height - scaledCropHeight) / 2 – Teddichiiwa Oct 06 '22 at 17:25
3

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
}
Community
  • 1
  • 1
Brandon A
  • 8,153
  • 3
  • 42
  • 77
3

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!
    }
}
BilalReffas
  • 8,132
  • 4
  • 50
  • 71
3

You can just crop using:

let croppedImage = yourImage.cgImage.cropping(to:rect)
croxy
  • 4,082
  • 9
  • 28
  • 46
Ramesh Sain
  • 31
  • 1
  • 3
3

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

madx
  • 6,723
  • 4
  • 55
  • 59
2

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
Asinox
  • 6,747
  • 14
  • 61
  • 89
PAD
  • 2,240
  • 1
  • 22
  • 28
2

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.

Kamil Harasimowicz
  • 4,684
  • 5
  • 32
  • 58
2

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)
    }
}
iPera
  • 1,053
  • 1
  • 12
  • 24
1

Change this:

masklayer.frame = ImgView.frame

To this:

masklayer.frame = ImgView.bounds
Fujia
  • 1,232
  • 9
  • 14
1

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)
0

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.

SMarx
  • 19
  • 2