如何从Swift中的一系列图像中有效地创建多行照片拼贴 [英] How to efficiently create a multi-row photo collage from an array of images in Swift

查看:135
本文介绍了如何从Swift中的一系列图像中有效地创建多行照片拼贴的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题

我正在构建一系列照片拼贴画,这些照片是我放在桌面视图上的。当图像数量达到tableview单元格宽度的边界时,我想让图像换行(这样我就可以在拼贴画中显示图像行)。目前我得到一排。如果需要其他信息,请随时告知。我很可能不会以最有效的方式接近这个,因为随着阵列中使用的图像数量开始增加而存在延迟。 (对此的任何反馈都将非常感激。)

I am building a collage of photos from an array of images that I am placing onto a tableview. I want to make the images wrap when the number of images reaches the boundary of the tableview cell's width (this would allow me to display rows of images in the collage). Currently I get a single row. Please feel free to advise if additional information is required. I am most likely not approaching this in the most efficient way since there is a delay as the number of images used in the array begins to increase. (any feedback on this would be very much appreciated).

Nota Bene


我正在创建拼贴图像。它实际上是一个图像。我想
通过在内存中创建一个有效的列和行
矩阵来排列拼贴。然后我用图像填充这些图像。最后,我对生成的图像进行快照,并在需要时使用它。该算法的写入效率不是
,只产生一行图像。我需要
替代下面使用的算法的轻量级替代品。我不认为在这种情况下UICollectionView将是一个有用的替代方案。

I am creating a collage image. It is actually one image. I want to arrange the collage by creating an efficent matrix of columns and rows in memory. I then fill these rects with images. Finally I snapshot the resulting image and use it when needed. The algorithm is not efficient as written and produces only a single row of images. I need a lightweight alternative to the algorithm used below. I do not believe UICollectionView will be a useful alternative in this case.

Pseudo Code


  1. 给定一组图像和一个目标矩形(代表
    目标视图)

  2. 获取阵列中的图像数量与每行允许的最大数量相比

  3. 定义一个适当大小的较小矩形来保存图像(所以
    每行填充图像目标矩形,即 - 如果一个图像然后应该填满该行;如果9个图像那么应该完全填满该行;如果10个图像每行最多9个图像,那么第10个开始第二行)

  4. 对集合进行迭代

  5. 将每个矩形从左到右放置在正确的位置
    ,直到达到最后一个图像或每行最大数量;继续下一行,直到所有图像都适合目标矩形

  6. 当每行达到最大图像数时,放置图像并设置下一个矩形以显示在连续图像上行

  1. Given an array of images and a target rectangle (representing the target view)
  2. Get the number of images in the array compared to max number allowed per row
  3. Define a smaller rectangle of appropriate size to hold the image (so that each row fills the target rectangle, i.e. - if one image then that should fill the row; if 9 images then that should fill the row completely; if 10 images with a max of 9 images per row then the 10th begins the second row)
  4. Iterate over the collection
  5. Place each rectangle at the correct location from left to right until either last image or a max number per row is reached; continue on next row until all images fit within the target rectangle
  6. When reaching a max number of images per row, place the image and setup the next rectangle to appear on the successive row

使用: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
    }

备选方案1

我已经完善了我的解决方案。然而,如上所述,这个将图像堆叠成行和行;我的兴趣在于尽可能提高效率。所呈现的是我尝试制作最简单的可行方法。

I've refined my solution to this a bit. This one does stack the images in columns and rows however, as stated; my interest is in making this as efficient as possible. What's presented is my attempt at producing the simplest possible thing that works.

警告

使用它生成的图像是倾斜的,而不是均匀分布在整个tableview单元格中。在tableview单元格中高效,均匀的分布将是最佳的。

The image produced using this is skewed rather than evenly distributed across the entire tableview cell. Efficient, even distribution across the tableview cell would be 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
    }

备选方案2

下面介绍的替代方案会对图像进行缩放,以便它们始终填充矩形(在我的情况下是tableview单元格)。随着添加更多图像,它们被缩放以适合矩形的宽度。当图像满足每行的最大图像数时,它们会换行。这是期望的行为,发生在内存中,相对较快,并且包含在我在UIImage类上扩展的简单类函数中。我仍然对任何能够更快地提供相同功能的算法感兴趣。

The alternative presented below scales the images so that they always fill the rectangle (in my case the tableview cell). As more images are added they are scaled to fit the width of the rectangle. When the images meet the maximum number of images per row, they wrap. This is the desired behavior, happens in memory, is relatively fast, and is contained in a simple class function that I extend on the UIImage class. I am still interested in any algorithm that can deliver the same functionality only faster.


Nota Bene:我不相信添加更多UI对于实现
效果非常有用如上所述。因此,一个更有效的编码算法是我想要的

Nota Bene: I do not believe adding more UI is useful to achieve the effects as noted above. Therefore a more efficient coding algorithm is what I am seeking.



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
    }

效率测试

Reda Lemeden提供了一些程序性的见解,了解如何在这个博客文章。他还指出了Andy Matuschak(UIKit团队)关于离屏渲染。我可能仍然没有正确利用CIImage解决方案,因为初始结果显示解决方案在尝试强制GPU利用时变慢。

Reda Lemeden gives some procedural insight into how to test these CG calls within Instruments on this blog post. He also points out some interesting notes from Andy Matuschak (of the UIKit team) about some of the peculiarities of off-screen rendering. I am probably still not leveraging the CIImage solution properly because initial results show the solution getting slower when attempting to force GPU utilization.

推荐答案

要在内存中构建拼贴,并尽可能高效,我建议查看核心图片。您可以组合多个CIF过滤器来创建输出图像。

To build the collage in memory, and to be as efficient as possible, I'd suggest looking into Core Image. You can combine multiple CIFilters to create your output image.

您可以应用 CIAffineTransform 过滤每个图像以排列它们(用 CICrop (如有必要),然后合并它们使用 CISourceOverCompositing 过滤器。在您要求输出之前,Core Image不会处理任何内容;因为这一切都发生在GPU上,所以它快速而有效。

You could apply CIAffineTransform filters to each of your images to line them up (cropping them to size with CICrop if necessary), then combine them using CISourceOverCompositing filters. Core Image doesn't process anything until you ask for the output; and because it's all happening in the GPU, it's fast and efficient.

这里有一些代码。为了理解,我尽量让它尽可能接近你的例子。如果我从头开始使用核心图像,我不一定要构建代码。

Here's a bit of code. I tried to keep it as close to your example as possible for the sake of understanding. It's not necessarily how I'd structure the code were I to use core image from scratch.

class func collageImage (rect: CGRect, images: [UIImage]) -> UIImage {

    let maxImagesPerRow = 3
    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

    var composite: CIImage? // used to hold the composite of the images

    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)
        }

        // Note, this section could be done with a single transform and perhaps increase the
        // efficiency a bit, but I wanted it to be explicit.
        //
        // It will also use the CI coordinate system which is bottom up, so you can translate
        // if the order of your collage matters.
        //
        // Also, note that this happens on the GPU, and these translation steps don't happen
        // as they are called... they happen at once when the image is rendered. CIImage can 
        // be thought of as a recipe for the final image.
        //
        // Finally, you an use core image filters for this and perhaps make it more efficient.
        // This version relies on the convenience methods for applying transforms, etc, but 
        // under the hood they use CIFilters
        var ci = CIImage(image: img)!

        ci = ci.imageByApplyingTransform(CGAffineTransformMakeScale(maxSide / img.size.width, maxSide / img.size.height))
        ci = ci.imageByApplyingTransform(CGAffineTransformMakeTranslation(smallRect.origin.x, smallRect.origin.y))!

        if composite == nil {

            composite = ci

        } else {

            composite = ci.imageByCompositingOverImage(composite!)
        }
    }

    let cgIntermediate = CIContext(options: nil).createCGImage(composite!, fromRect: composite!.extent())
    let finalRenderedComposite = UIImage(CGImage: cgIntermediate)!

    return finalRenderedComposite
}

你可能会发现你的CIImage旋转不正确。您可以使用以下代码更正它:

You may find that your CIImage is rotated incorrectly. You can correct it with code like the following:

var transform = CGAffineTransformIdentity

switch ci.imageOrientation {

case UIImageOrientation.Up:
    fallthrough
case UIImageOrientation.UpMirrored:
    println("no translation necessary. I am ignoring the mirrored cases because in my code that is ok.")
case UIImageOrientation.Down:
    fallthrough
case UIImageOrientation.DownMirrored:
    transform = CGAffineTransformTranslate(transform, ci.size.width, ci.size.height)
    transform = CGAffineTransformRotate(transform, CGFloat(M_PI))
case UIImageOrientation.Left:
    fallthrough
case UIImageOrientation.LeftMirrored:
    transform = CGAffineTransformTranslate(transform, ci.size.width, 0)
    transform = CGAffineTransformRotate(transform, CGFloat(M_PI_2))
case UIImageOrientation.Right:
    fallthrough
case UIImageOrientation.RightMirrored:
    transform = CGAffineTransformTranslate(transform, 0, ci.size.height)
    transform = CGAffineTransformRotate(transform, CGFloat(-M_PI_2))
}

ci = ci.imageByApplyingTransform(transform)

请注意,此代码忽略修复多个镜像案例。我会把它作为练习留给你,但它的主旨就在这里

Note that this code ignores fixing several mirrored cases. I'll leave that as an exercise up to you, but the gist of it is here.

如果您已经优化了Core Image处理,那么此时任何减速都可能是因为您将CIImage转换为UIImage;这是因为你的图像必须从GPU转换到CPU。如果要跳过此步骤以向用户显示结果,则可以。只需将结果直接渲染到GLKView即可。你总是可以在用户想要保存拼贴的时候转换到UIImage或CGImage。

If you've optimized your Core Image processing, then at this point any slowdown you see is probably due to transforming your CIImage into a UIImage; that's because your image has to make the transition from the GPU to the CPU. If you want to skip this step in order to display the results to the user, you can. Simply render your results to a GLKView directly. You can always transition to a UIImage or CGImage at the point the user wants to save the collage.

// this would happen during setup
let eaglContext = EAGLContext(API: .OpenGLES2)
glView.context = eaglContext

let ciContext = CIContext(EAGLContext: glView.context)

// this would happen whenever you want to put your CIImage on screen
if glView.context != EAGLContext.currentContext() {
    EAGLContext.setCurrentContext(glView.context)
}

let result = ViewController.collageImage(glView.bounds, images: images)

glView.bindDrawable()
ciContext.drawImage(result, inRect: glView.bounds, fromRect: result.extent())
glView.display()

这篇关于如何从Swift中的一系列图像中有效地创建多行照片拼贴的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆