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")
}
}
source to share
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:
This image is 1000x611. Here it looks like it is scaled down (but keep in mind that I will be using the original image):
My image, however, will be 139x182 and will be set to Aspect Fill. When it displays an image, it looks like this:
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):
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:
EDIT . Here's a downloadable sample project created as well:
But note that I cannot guarantee that the URL will last forever, so please read the above discussion to understand the approach used.
source to share