Cómo crear eficientemente un collage de fotos de varias filas a partir de una variedad de imágenes en Swift
Problema
Estoy construyendo un collage de fotos a partir de una serie de imágenes que estoy colocando en una vista de tabla. Quiero hacer que las imágenes se ajusten cuando el número de imágenes alcance el límite del ancho de la celda de vista de tabla (esto me permitiría mostrar filas de imágenes en el collage). Actualmente tengo una sola fila. No dude en avisar si se requiere información adicional. Lo más probable es que no aborde esto de la manera más eficiente ya que hay un retraso ya que el número de imágenes utilizadas en la matriz comienza a aumentar. (cualquier comentario sobre esto sería muy apreciado).
Nota Bene
Estoy creando una imagen de collage. En realidad es una imagen. Quiero organizar el collage creando una matriz eficiente de columnas y filasen memoria. Luego lleno estos rectos con imágenes. Finalmente tomo una foto de la imagen resultante y la uso cuando sea necesario. El algoritmo no es eficiente como está escrito y produce solo una sola fila de imágenes. Necesito una alternativa ligera al algoritmo utilizado a continuación. No creo que UICollectionView sea una alternativa útil en este caso.
Pseudocódigo
Dado un conjunto de imágenes y un rectángulo objetivo (que representa la vista objetivo)Obtenga el número de imágenes en la matriz en comparación con el número máximo permitido por filaDefina un rectángulo más pequeño del tamaño apropiado para contener la imagen (de modo que cada fila llene el rectángulo objetivo, es decir, si una imagen debería llenar la fila; si 9 imágenes, entonces debería llenar la fila por completo; si 10 imágenes con un máximo de 9 imágenes por fila, luego el 10 comienza la segunda fila)Iterar sobre la colecciónColoque cada rectángulo en la ubicación correcta de izquierda a derecha hasta alcanzar la última imagen o un número máximo por fila; continúe en la fila siguiente hasta que todas las imágenes encajen dentro del rectángulo objetivoCuando alcance un número máximo de imágenes por fila, coloque la imagen y configure el siguiente rectángulo para que aparezca en la fila sucesivaUtilizando: Swift 2.0
class func collageImage (rect:CGRect, images:[UIImage]) -> UIImage {
let maxSide = max(rect.width / CGFloat(images.count), rect.height / CGFloat(images.count))
UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale)
var xtransform:CGFloat = 0.0
for img in images {
let smallRect:CGRect = CGRectMake(xtransform, 0.0,maxSide, maxSide)
let rnd = arc4random_uniform(270) + 15
//draw in rect
img.drawInRect(smallRect)
//rotate img using random angle.
UIImage.rotateImage(img, radian: CGFloat(rnd))
xtransform += CGFloat(maxSide * 0.8)
}
let outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage
}
class func rotateImage(src: UIImage, radian:CGFloat) -> UIImage
{
// Calculate the size of the rotated view's containing box for our drawing space
let rotatedViewBox = UIView(frame: CGRectMake(0,0, src.size.width, src.size.height))
let t: CGAffineTransform = CGAffineTransformMakeRotation(radian)
rotatedViewBox.transform = t
let rotatedSize = rotatedViewBox.frame.size
// Create the bitmap context
UIGraphicsBeginImageContext(rotatedSize)
let bitmap:CGContextRef = UIGraphicsGetCurrentContext()
// Move the origin to the middle of the image so we will rotate and scale around the center.
CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);
// Rotate the image context
CGContextRotateCTM(bitmap, radian);
// Now, draw the rotated/scaled image into the context
CGContextScaleCTM(bitmap, 1.0, -1.0);
CGContextDrawImage(bitmap, CGRectMake(-src.size.width / 2, -src.size.height / 2, src.size.width, src.size.height), src.CGImage)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
Alternativa 1
He refinado un poco mi solución a esto. Sin embargo, éste apila las imágenes en columnas y filas, como se indicó; mi interés es hacer esto lo más eficiente posible. Lo que se presenta es mi intento de producir lo más simple posible que funcione.
Consideración
La imagen producida con esto está sesgada en lugar de distribuirse uniformemente en toda la celda de vista de tabla. La distribución eficiente y uniforme en la celda de vista de tabla sería óptima.
class func collageImage (rect:CGRect, images:[UIImage]) -> UIImage {
let maxSide = max(rect.width / CGFloat(images.count), rect.height / CGFloat(images.count)) //* 0.80
//let rowHeight = rect.height / CGFloat(images.count) * 0.8
let maxImagesPerRow = 9
var index = 0
var currentRow = 1
var xtransform:CGFloat = 0.0
var ytransform:CGFloat = 0.0
var smallRect:CGRect = CGRectZero
UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale)
for img in images {
let x = ++index % maxImagesPerRow //row should change when modulus is 0
//row changes when modulus of counter returns zero @ maxImagesPerRow
if x == 0 {
//last column of current row
//xtransform += CGFloat(maxSide)
smallRect = CGRectMake(xtransform, ytransform, maxSide, maxSide)
//reset for new row
++currentRow
xtransform = 0.0
ytransform = (maxSide * CGFloat(currentRow - 1))
} else {
//not a new row
if xtransform == 0 {
//this is first column
//draw rect at 0,ytransform
smallRect = CGRectMake(xtransform, ytransform, maxSide, maxSide)
xtransform += CGFloat(maxSide)
} else {
//not the first column so translate x, ytransform to be reset for new rows only
smallRect = CGRectMake(xtransform, ytransform, maxSide, maxSide)
xtransform += CGFloat(maxSide)
}
}
//draw in rect
img.drawInRect(smallRect)
}
let outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage
}
Alternativa 2
La alternativa presentada a continuación escala las imágenes para que siempre llenen el rectángulo (en mi caso, la celda de vista de tabla). A medida que se agregan más imágenes, se ajustan para ajustarse al ancho del rectángulo. Cuando las imágenes alcanzan el número máximo de imágenes por fila, se ajustan. Este es el comportamiento deseado, ocurre en la memoria, es relativamente rápido y está contenido en una función de clase simple que extiendo en la clase UIImage. Todavía estoy interesado en cualquier algoritmo que pueda ofrecer la misma funcionalidad solo más rápido.
Nota Bene: No creo que agregar más IU sea útil para lograr los efectos como se indicó anteriormente. Por lo tanto, un algoritmo de codificación más eficiente es lo que estoy buscando.
class func collageImage (rect:CGRect, images:[UIImage]) -> UIImage {
let maxImagesPerRow = 9
var maxSide : CGFloat = 0.0
if images.count >= maxImagesPerRow {
maxSide = max(rect.width / CGFloat(maxImagesPerRow), rect.height / CGFloat(maxImagesPerRow))
} else {
maxSide = max(rect.width / CGFloat(images.count), rect.height / CGFloat(images.count))
}
var index = 0
var currentRow = 1
var xtransform:CGFloat = 0.0
var ytransform:CGFloat = 0.0
var smallRect:CGRect = CGRectZero
UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale)
for img in images {
let x = ++index % maxImagesPerRow //row should change when modulus is 0
//row changes when modulus of counter returns zero @ maxImagesPerRow
if x == 0 {
//last column of current row
smallRect = CGRectMake(xtransform, ytransform, maxSide, maxSide)
//reset for new row
++currentRow
xtransform = 0.0
ytransform = (maxSide * CGFloat(currentRow - 1))
} else {
//not a new row
smallRect = CGRectMake(xtransform, ytransform, maxSide, maxSide)
xtransform += CGFloat(maxSide)
}
//draw in rect
img.drawInRect(smallRect)
}
let outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage
}
Pruebas de eficiencia
Reda Lemeden da una idea de procedimiento sobre cómo probar estas llamadas CG dentro de los instrumentos en esteentrada en el blog. También señala algunas notas interesantes de Andy Matuschak (del equipo de UIKit) sobre algunas de las peculiaridades derepresentación fuera de la pantalla. Probablemente todavía no estoy aprovechando la solución CIImage correctamente porque los resultados iniciales muestran que la solución se vuelve más lenta cuando intento forzar la utilización de la GPU.