Come ritagliare UIImageView su una nuova UIImmagine in modalità ‘riempimento aspetto’?

Sto cercando di ritagliare un’immagine secondaria di una vista immagine utilizzando un UIView sovrapposto che può essere posizionato ovunque in UIImageView . Sto prendendo in prestito una soluzione da un post simile su come risolvere questo problema quando la modalità di contenuto UIImageView è ‘Aspect Fit’. Quella soluzione proposta è:

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

Il problema è che l’utilizzo di questa soluzione quando la vista dell’immagine è a riempimento dell’aspetto fa sì che il segmento ritagliato non si allinea esattamente con il UIView stata posizionata la sovrapposizione di UIView . Non sono abbastanza sicuro su come adattare questo codice per ospitare Aspect Fill o riposizionare il mio overlay UIView modo che si allinei 1: 1 con il segmento che sto cercando di ritagliare.

AGGIORNAMENTO Risolto usando la risposta di Matt sotto

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

Il risultato finale inserisci la descrizione dell'immagine qui

Dividiamo il problema in due parti:

  1. Data la dimensione di UIImageView e le dimensioni della sua UIImage, se la modalità di contenuto di UIImageView è Aspect Fill, qual è la parte di UIImage che si adatta a UIImageView? Abbiamo bisogno, in effetti, di ritagliare l’immagine originale in modo che corrisponda a ciò che UIImageView sta effettivamente visualizzando.

  2. Dato un rect arbitrario all’interno di UIImageView, quale parte dell’immagine ritagliata (derivata nella parte 1) corrisponde?

La prima parte è la parte interessante, quindi proviamo. (La seconda parte sarà quindi banale.)

Ecco l’immagine originale che userò:

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

Quell’immagine è 1000×611. Ecco come appare ridimensionato (ma tieni presente che utilizzerò l’immagine originale per intero):

inserisci la descrizione dell'immagine qui

La mia vista immagine, tuttavia, sarà 139 x 182 ed è impostata su Aspect Fill. Quando visualizza l’immagine, assomiglia a questo:

inserisci la descrizione dell'immagine qui

Il problema che vogliamo risolvere è: quale parte dell’immagine originale viene visualizzata nella mia vista immagine, se la mia vista immagine è impostata su Aspect Fill?

Eccoci qui. Supponi che iv sia la vista dell’immagine:

 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) 

Così ora abbiamo risolto il problema: croppedImrect è la regione dell'immagine originale che viene visualizzata nella vista dell'immagine. Procediamo ad usare le nostre conoscenze, in realtà ritagliando l'immagine su una nuova immagine che corrisponde a quanto mostrato nella vista dell'immagine:

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

Il risultato è questa immagine (ignora il bordo grigio):

inserisci la descrizione dell'immagine qui

Ma ecco, questa è la risposta corretta! Ho estratto dall'immagine originale esattamente la regione ritratta all'interno della vista dell'immagine.

Quindi ora hai tutte le informazioni di cui hai bisogno. croppedIm è l'UIImage effettivamente visualizzato nell'area ritagliata della vista dell'immagine. scale è la scala tra la visualizzazione dell'immagine e quell'immagine. Pertanto, puoi facilmente risolvere il problema che hai originariamente proposto! Dato qualsiasi rettangolo imposto sulla vista dell'immagine, nelle coordinate dei limiti della vista dell'immagine, si applica semplicemente la scala (cioè dividi tutti e quattro i suoi attributi per scale ) - e ora hai lo stesso rettangolo di una porzione di croppedIm .

(Si osservi che non avevamo davvero bisogno di ritagliare l'immagine originale per ottenere il croppedIm , era sufficiente, in realtà, sapere come eseguire quel raccolto.L'importante informazione è la scale insieme origin di croppedImRect ; puoi prendere il rettangolo imposto sulla vista dell'immagine, ridimensionarlo e sfalsarlo per ottenere il rettangolo desiderato dell'immagine originale.)


EDIT Ho aggiunto un piccolo screencast solo per dimostrare che il mio approccio funziona come una prova di concetto:

inserisci la descrizione dell'immagine qui

EDIT Creato anche un progetto di esempio scaricabile qui:

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

Tuttavia, tieni presente che non posso garantire che l'URL duri per sempre, quindi leggi la discussione sopra per capire l'approccio utilizzato.