swiftuiimageviewcropapple-vision

Cropping visible part of UIImage in UIImageView for saliency


I'm doing attentionBased saliency and should pass image to the request. When the contentMode is ScaleAspectFill, the result of the request is not correct, because I use full image (not visible on screen part)

I'm trying to crop UIImage, but this method doesn't crop correctly

 let newImage = cropImage(imageToCrop: imageView.image, toRect: imageView.frame)


 func cropImage(imageToCrop: UIImage?, toRect rect: CGRect) -> UIImage? {
        guard let imageRef = imageToCrop?.cgImage?.cropping(to: rect) else {
            return nil
        }
        let cropped: UIImage = UIImage(cgImage: imageRef)
        return cropped
    }

How can I make saliency request only for the visible part of the image (which changes when change contentMode)?


Solution

  • If I understand your goal correctly...

    Suppose we have this 640 x 360 image:

    enter image description here

    and we display it in a 240 x 240 image view, using .scaleAspectFill...

    It looks like this (the red outline is the image view frame):

    enter image description here

    and, with .clipsToBounds = true:

    enter image description here

    we want to generate this new 360 x 360 image (that is, we want to keep the original image resolution... we don't want to end up with a 240 x 240 image):

    enter image description here

    To crop the visible portion of the image, we need to calculate the scaled rect, including the offset:

    func cropImage(imageToCrop: UIImage?, toRect rect: CGRect) -> UIImage? {
        guard let imageRef = imageToCrop?.cgImage?.cropping(to: rect) else {
            return nil
        }
        let cropped: UIImage = UIImage(cgImage: imageRef)
        return cropped
    }
    
    func myCrop(imgView: UIImageView) -> UIImage? {
        
        // get the image from the imageView
        guard let img = imgView.image else { return nil }
        
        // image view rect
        let vr: CGRect = imgView.bounds
        
        // image size -- we need to account for scale
        let imgSZ: CGSize = CGSize(width: img.size.width * img.scale, height: img.size.height * img.scale)
        
        let viewRatio: CGFloat = vr.width / vr.height
        let imgRatio: CGFloat = imgSZ.width / imgSZ.height
        
        var newRect: CGRect = .zero
        
        // calculate the rect that needs to be clipped from the full image
        
        if viewRatio > imgRatio {
            
            // image has a wider aspect ratio than the image view
            //  so top and bottom will be clipped
            let f: CGFloat = imgSZ.width / vr.width
            let h: CGFloat = vr.height * f
            newRect.origin.y = (imgSZ.height - h) * 0.5
            newRect.size.width = imgSZ.width
            newRect.size.height = h
            
        } else {
            
            // image has a narrower aspect ratio than the image view
            //  so left and right will be clipped
            let f: CGFloat = imgSZ.height / vr.height
            let w: CGFloat = vr.width * f
            newRect.origin.x = (imgSZ.width - w) * 0.5
            newRect.size.width = w
            newRect.size.height = imgSZ.height
            
        }
        
        return cropImage(imageToCrop: img, toRect: newRect)
    }
    

    and call it like this:

    if let croppedImage = myCrop(imgView: theImageView) {
        // do something with the new image
    }