ios - animatedimage - objective c uiimage
¿Cómo recortar un UIImageView a un nuevo UIImage en modo ''relleno de aspecto''? (1)
Dividamos el problema en dos partes:
-
Dado el tamaño de un UIImageView y el tamaño de su UIImage, si el modo de contenido del UIImageView es Aspect Fill, ¿cuál es la parte del UIImage que se ajusta al UIImageView? Necesitamos, en efecto, recortar la imagen original para que coincida con lo que realmente está mostrando el UIImageView.
-
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 probémoslo. (La segunda parte resultará trivial).
Aquí está la imagen original que usaré:
Esa imagen es 1000x611. Esto es lo que parece reducido (pero tenga en cuenta que voy a usar la imagen original en todo momento):
Sin embargo, mi vista de imagen será 139x182 y está configurada en Relleno de aspecto. Cuando muestra la imagen, se ve así:
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.
Suponga que
iv
es la vista de 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 imagen.
Procedamos a usar nuestro conocimiento, recortando la imagen a una nueva imagen que coincida con lo que se muestra en la vista de 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):
Pero he aquí, ¡esa es la respuesta correcta! Extraje de la imagen original exactamente la región representada en el interior de la vista de imagen.
Así que ahora tienes toda la información que necesitas.
croppedIm
es el UIImage que se muestra realmente en el área recortada de la vista de imagen.
scale
es la escala entre la vista de imagen y esa imagen.
¡Por lo tanto, puede resolver fácilmente el problema que propuso 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 la
scale
), y ahora tiene el mismo rectángulo que una parte de
croppedIm
.
(Observe que
realmente
no necesitábamos recortar la imagen original para
croppedIm
; en realidad, era suficiente saber
cómo
realizar ese recorte. 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 imagen, escalarlo
y compensarlo
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:
EDITAR También creó un proyecto de ejemplo descargable aquí:
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.
Estoy tratando de recortar una
UIView
de una vista de imagen usando una superposición
UIView
que se puede colocar en cualquier lugar de
UIImageView
.
Estoy tomando prestada una solución de una publicación similar sobre cómo resolver esto cuando el modo de contenido UIImageView es ''Aspect Fit''.
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 imagen es de relleno de aspecto hace que el segmento recortado no se alinee exactamente con el lugar donde se
UIView
la superposición
UIView
.
No estoy muy 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")
}
}