How to crop UIImageView to new UIImage mode in "fill aspect" mode?

I am trying to crop a sub-image of an image using an overlay UIView

that can be positioned anywhere UIImageView

. I am borrowing the solution from a similar post on how to solve this when the content mode of the UIImageView is "Aspect Fit". This is the suggested solution:

  func computeCropRect(for sourceFrame : CGRect) -> CGRect {

        let widthScale = bounds.size.width / image!.size.width
        let heightScale = bounds.size.height / image!.size.height

        var x : CGFloat = 0
        var y : CGFloat = 0
        var width : CGFloat = 0
        var height : CGFloat = 0
        var offSet : CGFloat = 0

        if widthScale < heightScale {
            offSet = (bounds.size.height - (image!.size.height * widthScale))/2
            x = sourceFrame.origin.x / widthScale
            y = (sourceFrame.origin.y - offSet) / widthScale
            width = sourceFrame.size.width / widthScale
            height = sourceFrame.size.height / widthScale
        } else {
            offSet = (bounds.size.width - (image!.size.width * heightScale))/2
            x = (sourceFrame.origin.x - offSet) / heightScale
            y = sourceFrame.origin.y / heightScale
            width = sourceFrame.size.width / heightScale
            height = sourceFrame.size.height / heightScale
        }

        return CGRect(x: x, y: y, width: width, height: height)
    }

      

The problem is that using this solution when the image is a fill aspect results in the cropped segment not aligning exactly where the overlay was located UIView

. I'm not really sure how to adapt this code to accommodate the Aspect Fill, or move my overlay UIView

to align 1: 1 with the segment I'm trying to crop.

UPDATE Decided to use Matt below

class ViewController: UIViewController {

    @IBOutlet weak var catImageView: UIImageView!
    private var cropView : CropView!

    override func viewDidLoad() {
        super.viewDidLoad()

        cropView = CropView(frame: CGRect(x: 0, y: 0, width: 45, height: 45))

        catImageView.image = UIImage(named: "cat")
        catImageView.clipsToBounds = true

        catImageView.layer.borderColor = UIColor.purple.cgColor
        catImageView.layer.borderWidth = 2.0
        catImageView.backgroundColor = UIColor.yellow

        catImageView.addSubview(cropView)

        let imageSize = catImageView.image!.size
        let imageViewSize = catImageView.bounds.size

        var scale : CGFloat = imageViewSize.width / imageSize.width

        if imageSize.height * scale < imageViewSize.height {
            scale = imageViewSize.height / imageSize.height
        }

        let croppedImageSize = CGSize(width: imageViewSize.width/scale, height: imageViewSize.height/scale)

        let croppedImrect =
            CGRect(origin: CGPoint(x: (imageSize.width-croppedImageSize.width)/2.0,
                                   y: (imageSize.height-croppedImageSize.height)/2.0),
                   size: croppedImageSize)

        let renderer = UIGraphicsImageRenderer(size:croppedImageSize)

        let _ = renderer.image { _ in
            catImageView.image!.draw(at: CGPoint(x:-croppedImrect.origin.x, y:-croppedImrect.origin.y))
        }
    }


    @IBAction func performCrop(_ sender: Any) {
        let cropFrame = catImageView.computeCropRect(for: cropView.frame)
        if let imageRef = catImageView.image?.cgImage?.cropping(to: cropFrame) {
            catImageView.image = UIImage(cgImage: imageRef)
        }
    }

    @IBAction func resetCrop(_ sender: Any) {
        catImageView.image = UIImage(named: "cat")
    }
}

      

Final result enter image description here

+3


source to share


1 answer


Let's divide the task into two parts:

  • Given the size of the UIImageView and the size of its UIImage, if the content mode of the UIImageView is Aspect Fill , what is the part of the UIImage that fits into the UIImageView? We need to essentially crop the original image to match what the UIImageView actually displays.

  • Given an arbitrary rectangle inside the UIImageView, what part of the cropped image (obtained in part 1) matches?

The first part is the fun part, so give it a try. (The second part will be trivial.)

Here's the original image I'll be using:

https://static1.squarespace.com/static/54e8ba93e4b07c3f655b452e/t/56c2a04520c64707756f4267/1455596221531/

This image is 1000x611. Here it looks like it is scaled down (but keep in mind that I will be using the original image):

enter image description here

My image, however, will be 139x182 and will be set to Aspect Fill. When it displays an image, it looks like this:

enter image description here

The problem we want to solve is this: how much of the original image is displayed in my image view if my image view is set to Aspect Fill?

Here we go. Suppose iv

- this is the kind of image:

let imsize = iv.image!.size
let ivsize = iv.bounds.size

var scale : CGFloat = ivsize.width / imsize.width
if imsize.height * scale < ivsize.height {
    scale = ivsize.height / imsize.height
}

let croppedImsize = CGSize(width:ivsize.width/scale, height:ivsize.height/scale)
let croppedImrect =
    CGRect(origin: CGPoint(x: (imsize.width-croppedImsize.width)/2.0,
                           y: (imsize.height-croppedImsize.height)/2.0),
           size: croppedImsize)

      

So now we have solved the problem: croppedImrect

is the area of ​​the original image that is displayed in the image view. Let's continue to use our knowledge by actually cropping the image to a new image that matches what is shown in the image:



let r = UIGraphicsImageRenderer(size:croppedImsize)
let croppedIm = r.image { _ in
    iv.image!.draw(at: CGPoint(x:-croppedImrect.origin.x, y:-croppedImrect.origin.y))
}

      

The result is this image (ignore the gray border):

enter image description here

But here's the correct answer! I extracted from the original image exactly the region that is shown inside the image.

So now you have all the information you need. croppedIm

is the UIImage actually displayed in the cropped area of ​​the image. scale

- the scale between the image and the image. Therefore, you can easily solve the problem you originally suggested! With any rectangle overlaid on the image, in image view coordinates, you simply apply the scale (i.e. Divide all four of its attributes by scale

) - and now you have the same rectangle as the part croppedIm

.

(Note that we really don't need to crop the original image to get it croppedIm

, in reality it was enough to know how to crop it . The important information scale

along with origin

of croppedImrect

, given this information, you can take a rectangle overlaid on the image, scale it and offset to get the desired rectangle of the original image.)


EDIT I've added a small screencast to show that my approach works as a proof of concept:

enter image description here

EDIT . Here's a downloadable sample project created as well:

https://github.com/mattneub/Programming-iOS-Book-Examples/blob/39cc800d18aa484d17c26ffcbab8bbe51c614573/bk2ch02p058cropImageView/Cropper/ViewController.swift

But note that I cannot guarantee that the URL will last forever, so please read the above discussion to understand the approach used.

+5


source







All Articles