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 sucessiva

Usando: 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.

questionAnswers(4)

yourAnswerToTheQuestion