Cómo recortar un UIImageView a un nuevo UIImage en modo ‘aspect fill’?

Estoy tratando de recortar una UIView de una vista de imagen usando una superposición UIView que se puede colocar en cualquier parte del UIImageView . Estoy tomando prestada una solución de una publicación similar sobre cómo resolver esto cuando el modo de contenido UIImageView es ‘Ajuste de aspecto’. Esa solución propuesta es:

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

El problema es que el uso de esta solución cuando la vista de la imagen es un relleno de aspecto hace que el segmento recortado no se alinee exactamente con el lugar donde se UIView la superposición UIView . No estoy seguro de cómo adaptar este código para acomodar Aspect Fill o reposicionar mi superposición UIView para que se alinee 1: 1 con el segmento que estoy tratando de recortar.

ACTUALIZACIÓN Resuelto usando la respuesta de Matt a continuación

 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") } } 

El resultado final enter image description here

Dividamos el problema en dos partes:

  1. Dado el tamaño de un UIImageView y el tamaño de su UIImage, si el modo de contenido de UIImageView es Aspect Fill, ¿cuál es la parte del UIImage que cabe en UIImageView? Necesitamos, en efecto, recortar la imagen original para que coincida con lo que UIImageView realmente muestra.

  2. Dado un rect arbitrario dentro de UIImageView, ¿a qué parte de la imagen recortada (derivada en la parte 1) corresponde?

La primera parte es la parte interesante, así que probemos. (La segunda parte resultará ser trivial).

Aquí está la imagen original que usaré:

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

Esa imagen es 1000×611. Esto es lo que parece escalado (pero tenga en cuenta que voy a utilizar la imagen original ):

enter image description here

Mi vista de imagen, sin embargo, será 139×182 y está configurada en Relleno de aspecto. Cuando muestra la imagen, se ve así:

enter image description here

El problema que queremos resolver es: ¿qué parte de la imagen original se muestra en mi vista de imagen si mi vista de imagen está configurada en Relleno de aspecto?

Aquí vamos. Supongamos que iv es la vista de la imagen:

 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) 

Así que ahora hemos resuelto el problema: croppedImrect es la región de la imagen original que se muestra en la vista de la imagen. Procedamos a utilizar nuestro conocimiento, recortando la imagen en una nueva imagen que coincida con lo que se muestra en la vista de la imagen:

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

El resultado es esta imagen (ignore el borde gris):

enter image description here

Pero he aquí, ¡esa es la respuesta correcta! Extraje de la imagen original exactamente la región retratada en el interior de la vista de imagen.

Entonces ahora tiene toda la información que necesita. croppedIm es realmente el UIImage que se muestra en el área recortada de la vista de la imagen. scale es la escala entre la vista de la imagen y esa imagen. ¡Por lo tanto, puedes resolver fácilmente el problema que propusiste originalmente! Dado cualquier rectángulo impuesto sobre la vista de la imagen, en las coordenadas de los límites de la vista de la imagen, simplemente aplica la escala (es decir, divide los cuatro atributos por scale ), y ahora tienes el mismo rectángulo que una parte de croppedIm .

(Observe que realmente no necesitamos recortar la imagen original para obtener croppedIm , era suficiente, en realidad, para saber cómo realizar ese cultivo. La información importante es la scale junto con el origin de croppedImRect ; dada esa información, puede tomar el rectángulo impuesto sobre la vista de la imagen, escalarla y desplazarla para obtener el rectángulo deseado de la imagen original).


EDITAR Agregué un pequeño screencast solo para mostrar que mi enfoque funciona como una prueba de concepto:

enter image description here

EDITAR También creó un proyecto de ejemplo descargable aquí:

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

Pero tenga en cuenta que no puedo garantizar que la URL dure para siempre, así que lea la discusión anterior para comprender el enfoque utilizado.