So erstellen Sie effizient eine mehrzeilige Fotocollage aus einer Reihe von Bildern in Swift

Proble

Ich erstelle eine Collage von Fotos aus einer Reihe von Bildern, die ich auf einer Tabellenansicht platziere. Ich möchte, dass die Bilder umbrochen werden, wenn die Anzahl der Bilder die Grenze der Breite der Tabellenzelle erreicht (dies würde es mir ermöglichen, Bildzeilen in der Collage anzuzeigen). Zur Zeit bekomme ich eine einzelne Zeile. Bitte zögern Sie nicht zu beraten, wenn zusätzliche Informationen erforderlich sind. Ich gehe dies höchstwahrscheinlich nicht auf die effizienteste Weise an, da es eine Verzögerung gibt, da die Anzahl der im Array verwendeten Bilder zuzunehmen beginnt. (Rückmeldungen hierzu sind sehr willkommen.)

Nota Bene

Ich erstelle ein Collagenbild. Es ist eigentlich ein Bild. Ich möchte die Collage anordnen, indem ich eine effiziente Matrix aus Spalten und Zeilen erstelle.in Erinnerun. Diese Rechtecke fülle ich dann mit Bildern. Zum Schluss mache ich einen Schnappschuss des resultierenden Bildes und verwende es bei Bedarf. Der Algorithmus ist nicht effizient wie geschrieben und erzeugt nur eine einzelne Reihe von Bildern. Ich brauche eine leichte Alternative zu dem unten verwendeten Algorithmus. Ich glaube nicht, dass UICollectionView in diesem Fall eine nützliche Alternative sein wird.

Pseudo Code

Gab ein Array von Bildern und ein Zielrechteck (das die Zielansicht darstellt)Lesen Sie die Anzahl der Bilder im Array im Vergleich zur maximal zulässigen Anzahl pro Zeile Definieren Sie ein kleineres Rechteck mit der entsprechenden Größe, um das Bild aufzunehmen (so dass jede Zeile das Zielrechteck ausfüllt, dh - wenn ein Bild die Zeile ausfüllt, wenn 9 Bilder die Zeile vollständig ausfüllt, wenn 10 Bilder mit einem Maximal 9 Bilder pro Reihe, dann beginnt die 10. Reihe mit der zweiten)Über die Sammlung iterieren Platzieren Sie jedes Rechteck von links nach rechts an der richtigen Stelle, bis entweder das letzte Bild oder eine maximale Anzahl pro Zeile erreicht ist. Fahren Sie mit der nächsten Zeile fort, bis alle Bilder in das Zielrechteck passen.Bei Erreichen einer maximalen Anzahl von Bildern pro Zeile platzieren Sie das Bild und richten das nächste Rechteck ein, das in der nachfolgenden Zeile angezeigt wird.

Using: 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
    }

Alternative 1

Ich habe meine Lösung dazu etwas verfeinert. In diesem Fall werden die Bilder jedoch wie angegeben in Spalten und Zeilen gestapelt. Mein Interesse ist es, dies so effizient wie möglich zu gestalten. Was präsentiert wird, ist mein Versuch, das einfachste zu produzieren, was funktioniert.

Vorbehal

Das mit dieser Funktion erzeugte Bild ist nicht gleichmäßig über die gesamte Tabellenzelle verteilt, sondern verzerrt. Eine effiziente, gleichmäßige Verteilung auf die Tabellenzelle wäre optimal.

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
    }

Alternative 2

Die unten dargestellte Alternative skaliert die Bilder so, dass sie immer das Rechteck ausfüllen (in meinem Fall die Tabellenzelle). Wenn weitere Bilder hinzugefügt werden, werden diese an die Breite des Rechtecks angepasst. Wenn die Bilder die maximale Anzahl von Bildern pro Zeile erreichen, werden sie umbrochen. Dies ist das gewünschte Verhalten, das im Speicher auftritt, relativ schnell ist und in einer einfachen Klassenfunktion enthalten ist, die ich für die UIImage-Klasse erweitere. Ich bin immer noch an einem Algorithmus interessiert, der die gleiche Funktionalität nur schneller liefern kann.

Nota Bene: Ich glaube nicht, dass das Hinzufügen weiterer Benutzeroberflächen nützlich ist, um die oben genannten Effekte zu erzielen. Deshalb suche ich nach einem effizienteren Kodierungsalgorithmus.

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
    }

Effizienzprüfung

Reda Lemeden gibt einen Einblick in die Vorgehensweise zum Testen dieser CG-Aufrufe in Instruments auf diesemBlogeintra. Er weist auch auf einige interessante Notizen von Andy Matuschak (vom UIKit-Team) zu einigen Besonderheiten von @ hi Off-Screen-Rendering. Ich nutze die CIImage-Lösung wahrscheinlich immer noch nicht richtig, da die Lösung nach ersten Ergebnissen langsamer wird, wenn versucht wird, die GPU-Nutzung zu erzwingen.