Como criar com eficiência uma colagem de fotos com várias linhas a partir de uma matriz de imagens no Swift
Problema
Estou construindo uma colagem de fotos a partir de uma variedade de imagens que estou colocando em uma tableview. Quero fazer com que as imagens sejam quebradas quando o número de imagens atingir o limite da largura da célula da tableview (isso permitiria exibir linhas de imagens na colagem). Atualmente, recebo uma única linha. Por favor, não hesite em avisar se são necessárias informações adicionais. Provavelmente não estou abordando isso da maneira mais eficiente, pois há um atraso à medida que o número de imagens usadas na matriz começa a aumentar. (qualquer feedback sobre isso seria muito apreciado).
Nota Bene
Estou criando uma imagem de colagem. Na verdade, é uma imagem. Quero organizar a colagem criando uma matriz eficaz de colunas e linhasem memória. Depois, preencho essas imagens com imagens. Finalmente, eu tiro a imagem resultante e a uso quando necessário. O algoritmo não é eficiente como está escrito e produz apenas uma única linha de imagens. Eu preciso de uma alternativa leve para o algoritmo usado abaixo. Não acredito que o UICollectionView seja uma alternativa útil nesse caso.
Pseudo-código
Dada uma matriz de imagens e um retângulo de destino (representando a vista de destino)Obtenha o número de imagens na matriz em comparação com o número máximo permitido por linhaDefina um retângulo menor do tamanho apropriado para manter a imagem (de modo que cada linha preencha o retângulo de destino, por exemplo - se uma imagem for preenchida; se 9 imagens, preenchê-la por completo; se 10 imagens com um máximo de 9 imagens por linha, a décima começa a segunda linha)Iterar sobre a coleçãoColoque cada retângulo no local correto da esquerda para a direita até a última imagem ou um número máximo por linha; continue na próxima linha até que todas as imagens caibam dentro do retângulo de destinoAo atingir um número máximo de imagens por linha, coloque a imagem e configure o próximo retângulo para aparecer na linha sucessivaUsando: 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
Eu refinei minha solução um pouco. Este empilhar as imagens em colunas e linhas no entanto, como indicado; meu interesse é tornar isso o mais eficiente possível. O que é apresentado é a minha tentativa de produzir a coisa mais simples possível que funciona.
Embargo
A imagem produzida usando isso é distorcida em vez de distribuída uniformemente por toda a célula da tableview. Uma distribuição eficiente e uniforme na célula da tableview seria ideal.
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
A alternativa apresentada abaixo dimensiona as imagens para que elas sempre preencham o retângulo (no meu caso, a célula de tableview). À medida que mais imagens são adicionadas, elas são dimensionadas para se ajustarem à largura do retângulo. Quando as imagens atingem o número máximo de imagens por linha, elas são quebradas. Esse é o comportamento desejado, acontece na memória, é relativamente rápido e está contido em uma função de classe simples que eu estendo na classe UIImage. Ainda estou interessado em qualquer algoritmo que possa fornecer a mesma funcionalidade apenas mais rapidamente.
Nota Bene: Não acredito que adicionar mais interface do usuário seja útil para obter os efeitos, conforme observado acima. Portanto, um algoritmo de codificação mais eficiente é o que estou procurando.
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
}
Teste de eficiência
Reda Lemeden fornece algumas dicas processuais sobre como testar essas chamadas de CG dentro da Instrumentspublicação no blog. Ele também aponta algumas notas interessantes de Andy Matuschak (da equipe UIKit) sobre algumas das peculiaridades derenderização fora da tela. Provavelmente ainda não estou aproveitando a solução CIImage corretamente, porque os resultados iniciais mostram que a solução está ficando mais lenta ao tentar forçar a utilização da GPU.