1

I am having trouble cropping pictures taken to be of an exact size that is in the wide format. For instance I take a picture with an iPad front camera, which has the resolution of 960w,1280h and I need to crop to be exactly 875w,570h. I tried some of the methods in here, but they all stretch the image or don't get the size I want.

Here is the first method that I tried:

func cropToBounds(image: UIImage, width: Double, height: Double) -> UIImage {

    let cgimage = image.cgImage!
    let contextImage: UIImage = UIImage(cgImage: cgimage)
    guard let newCgImage = contextImage.cgImage else { return contextImage }
    let contextSize: CGSize = contextImage.size
    var posX: CGFloat = 0.0
    var posY: CGFloat = 0.0
    let cropAspect: CGFloat = CGFloat(width / height)

    var cropWidth: CGFloat = CGFloat(width)
    var cropHeight: CGFloat = CGFloat(height)

    if width > height { //Landscape
        cropWidth = contextSize.width
        cropHeight = contextSize.width / cropAspect
        posY = (contextSize.height - cropHeight) / 2
    } else if width < 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 contextImage}

    // Create a new image based on the imageRef and rotate back to the original orientation
    let cropped: UIImage = UIImage(cgImage: imageRef, scale: image.scale, orientation: image.imageOrientation)

    print(image.scale)
    UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), false, 0.0)
    cropped.draw(in: CGRect(x: 0, y: 0, width: width, height: height))
    let resized = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()


    return resized ?? image
}

This always stretches the image.

I thought about trying to cut the exact size I wanted, so I tried this:

func cropImage(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 recWidth : CGFloat = CGFloat(width)
    var recHeight : CGFloat = CGFloat(height)

    if width > height { //Landscape
         posY = (contextSize.height - recHeight) / 2
    }
    else { //Square

        posX = (contextSize.width - recWidth) / 2

    }

    let rect: CGRect = CGRect(x: posX, y: posY, width: recWidth, height: recHeight)
    let imageRef:CGImage = cgimage.cropping(to: rect)!
    print(imageRef.width)
    print(imageRef.height)
    let croppedimage:UIImage = UIImage(cgImage: imageRef, scale: image.scale, orientation: image.imageOrientation)
    print(croppedimage.size)



    return croppedimage
}

But this resulted in an image with the opposite of what I want, 570w,875h. So I thought about inverting the values, but if I do that I get 605w, 570h. Maybe the problem is in how I get the X and Y positions of the image?

EDIT

Here is what I am doing now after the help of Leo Dabus:

extension UIImage {
  func cropped(to size: CGSize) -> UIImage? {
    guard let cgImage = cgImage?
        .cropping(to: .init(origin: .init(x: (self.size.width-size.width)/2,
                                          y: (self.size.height-size.height)/2),
                            size: size)) else { return nil }
    let format = imageRendererFormat
    return UIGraphicsImageRenderer(size: size, format: format).image {
        _ in
        UIImage(cgImage: cgImage, scale: 1, orientation: imageOrientation)
            .draw(in: .init(origin: .zero, size: size))
    }

 }
}

This is how I call it:

let foto = UIImage(data: imageData)!
let size = CGSize(width: 875.0, height: 570.0)
let cropedPhoto = foto.cropped(to: size)

The imageData comes from a capture of the front camera.

And this is my capture code:

@objc func takePhoto(_ sender: Any?) {
    let videoOrientation = AVCaptureVideoOrientation.portrait
    stillImageOutput!.connection(with: .video)?.videoOrientation = videoOrientation

    let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
    let gesture = previewView.gestureRecognizers
    previewView.removeGestureRecognizer(gesture![0])
}

func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    guard let imageData = photo.fileDataRepresentation()
        else { return }                        
}
Bruno
  • 61
  • 8
  • Not related to your question but `cgImage` property of `UIImage` might return `nil`. Btw what's the purpose of getting a `CGImage` and right after that initialising another `UIImage` from that? – Leo Dabus Feb 04 '20 at 21:28
  • To be honest I only did that because in another stack overflow answer I saw some people doing that, so I thought about trying just to see if I would get something different, but it didn't make a difference. – Bruno Feb 04 '20 at 21:33

1 Answers1

3

You just need to get the original size width subtract the destination size width, divide by two and set the cropping origin x value. Next do the same with the height and set the y position. Then just initialize a new UIImage with the cropped cgImage:

extension UIImage {
    func cropped(to size: CGSize) -> UIImage? {
        guard let cgImage = cgImage?
            .cropping(to: .init(origin: .init(x: (self.size.width - size.width) / 2,
                                              y: (self.size.height - size.height) / 2),
                                size: size)) else { return nil }
        return UIImage(cgImage: cgImage, scale: 1, orientation: imageOrientation)
    }
}

let imageURL = URL(string: "https://www.comendochucruteesalsicha.com.br/wp-content/uploads/2016/09/IMG_5356-960x1280.jpg")!
let image = UIImage(data: try! Data(contentsOf: imageURL))!

let squared = image.cropped(to: .init(width: 875, height: 570))

enter image description here

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • Thanks for all the help so far, but I am still having some troubles, the resulted image has the inverted value of width and height. I noticed that the cgImage has the right values, width 875, height = 570, but when it goes to the UIImage, that's when they are inverted. – Bruno Feb 05 '20 at 14:36
  • 1
    You might need to render your image with a new context. Check this post https://stackoverflow.com/a/42098812/2303865 – Leo Dabus Feb 05 '20 at 14:36
  • So I tried rendering with a new context, but now the image is stretched. I removed the orientation just to se if it had any effect and got a perfect image with what I want, but inverted. It seams that using the image original orientation for some reason is why the width and height are getting inverted. Any ideas of how to solve that or why that is happening? – Bruno Feb 05 '20 at 15:40
  • 1
    You need to redraw/flatten your image before cropping it – Leo Dabus Feb 05 '20 at 16:25
  • I don't quite understand what do you mean by that. Should I use UIGraphicsImageRenderer before cropping to redraw the image, is that is? – Bruno Feb 05 '20 at 17:12
  • 1
    What happen is the UIImage has some metadata associated with it (including its orientation). The CGImage doesn't. When you render the image in a new context you are creating a new image with the proper orientation, therefore it doesn't depend on the metadata to be displayed correctly. The same happens with PNG. If you save a PNG without redrawing it it might not display properly depending on the orientation which the picture was taken. A JPEG image doesn't have this problem because it saves the metadata as well. – Leo Dabus Feb 05 '20 at 17:28
  • 1
    So just create a new UIImage using the flatten property of the link I posted above. Then you can get a CGImage from it and manipulate it (in your case crop it) without worrying about the orientation it was taken – Leo Dabus Feb 05 '20 at 17:30
  • 1
    Thanks so much, that was it! – Bruno Feb 05 '20 at 18:13