Ios 如何从Swift中的一组图像高效地创建多行照片拼贴

Ios 如何从Swift中的一组图像高效地创建多行照片拼贴,ios,performance,swift,processing-efficiency,Ios,Performance,Swift,Processing Efficiency,问题 我正在将一系列图片拼贴到桌面视图上。我想在图像数量达到tableview单元格宽度的边界时使图像换行(这将允许我在拼贴中显示图像行)。目前我只得到一行。如果需要更多信息,请随时提供建议。我很可能无法以最有效的方式实现这一点,因为随着阵列中使用的图像数量开始增加,会出现延迟。(如对此有任何反馈,将不胜感激) Nota Bene 我正在创建一个拼贴图像。它实际上是一个图像。我想 通过创建列和行的有效矩阵来排列拼贴 内存中。然后用图像填充这些矩形。最后,我快照生成的图像,并在需要时使用它。算法不

问题

我正在将一系列图片拼贴到桌面视图上。我想在图像数量达到tableview单元格宽度的边界时使图像换行(这将允许我在拼贴中显示图像行)。目前我只得到一行。如果需要更多信息,请随时提供建议。我很可能无法以最有效的方式实现这一点,因为随着阵列中使用的图像数量开始增加,会出现延迟。(如对此有任何反馈,将不胜感激)

Nota Bene

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

伪代码

  • 给定一个图像数组和一个目标矩形(表示 目标视图)
  • 获取与每行允许的最大数量相比的数组中的图像数
  • 定义一个适当大小的较小矩形以容纳图像(因此 每行填充目标矩形,即-如果有一个图像,则应填充该行;如果有9个图像,则应完全填充该行;如果有10个图像,且每行最多有9个图像,则第10个图像从第二行开始)
  • 迭代集合
  • 将每个矩形从左到右放置在正确的位置 直到达到最后一个图像或每行的最大数量;继续下一行,直到所有图像都符合目标矩形
  • 当达到每行的最大图像数时,放置图像并 设置要显示在连续行上的下一个矩形
  • 使用: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

    我已经改进了我的解决方案。但是,正如前面所述,这一个确实将图像堆叠在列和行中;我的兴趣是尽可能提高效率。这里介绍的是我试图制作最简单可行的东西

    警告

    使用此方法生成的图像是倾斜的,而不是均匀分布在整个tableview单元格中。高效、均匀地分布在tableview单元格中是最佳选择

    备选方案2

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

    没有好处:我不认为添加更多的UI有助于实现 如上所述的影响。因此,需要一种更有效的编码算法 我所追求的

    效率测试


    Reda Lemeden提供了一些程序性的见解,以了解如何在此基础上在仪器内测试这些CG调用。他还指出了一些来自安迪·马图切克(UIKit团队的成员)的有趣笔记,这些笔记讲述了。我可能仍然没有正确地利用CIImage解决方案,因为最初的结果表明,当试图强制使用GPU时,解决方案会变得越来越慢

    因为您使用的是Swift 2.0:完全符合您试图手动执行的操作,并且比UICollectionView更易于使用。假设您使用的是故事板,创建一个具有多个嵌套视图的原型TableViewCell应该完全满足您的需要。如果你想要的话,你只需要确保你插入的UIImages都是相同的纵横比

    您的算法效率极低,因为它必须在您从阵列中添加或删除图像时,使用多个核心动画变换重新绘制每个图像。支持动态添加和删除对象


    如果出于某种原因,您仍然需要将生成的拼贴快照为UIImage,.

    为了在内存中构建拼贴,并尽可能高效,我建议查看。您可以组合多个CIFILTER来创建输出映像

    您可以对每个图像应用过滤器,使其对齐(如有必要,将其裁剪为大小),然后使用过滤器将其组合。核心图像在您请求输出之前不会处理任何内容;因为这一切都发生在GPU上,所以它快速高效

    这里有一些代码。为了理解,我试着尽可能接近你的例子。如果我从零开始使用核心映像,我不一定会构造代码

    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
    }
    
    您可能会发现图像旋转不正确。您可以使用如下代码更正它:

    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)
    
    请注意,此代码忽略修复几个镜像案例。我将把它作为一个练习留给你,但是问题是

    如果您已经优化了核心图像处理,那么在这一点上,您看到的任何放缓都可能是由于将CIImage转换为UIImage;这是因为图像必须从GPU转换到CPU。如果要跳过此步骤以便向用户显示结果,可以。只需直接将结果呈现到GLKView即可。您始终可以在用户希望保存拼贴的位置转换为UIImage或CGImage

    // 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()
    

    基于上面提供的备选方案2,我创建了一个
    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)
    
    // 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()
    
    func collageImage(rect: CGRect, images: [UIImage]) -> UIImage {
        if images.count == 1 {
            return images[0]
        }
    
        UIGraphicsBeginImageContextWithOptions(rect.size, false,  UIScreen.mainScreen().scale)
    
        let nrofColumns: Int = max(2, Int(sqrt(Double(images.count-1)))+1)
        let nrOfRows: Int = (images.count)/nrofColumns
        let remaindingPics: Int = (images.count) % nrofColumns
        print("columns: \(nrofColumns) rows: \(nrOfRows) first \(remaindingPics) columns will have 1 pic extra")
    
        let w: CGFloat = rect.width/CGFloat(nrofColumns)
        var hForColumn = [CGFloat]()
        for var c=1;c<=nrofColumns;++c {
            if remaindingPics >= c {
                hForColumn.append(rect.height/CGFloat(nrOfRows+1))
            }
            else {
                hForColumn.append(rect.height/CGFloat(nrOfRows))
            }
        }
        var colNr = 0
        var rowNr = 0
        for var i=1; i<images.count; ++i {
            images[i].drawInRectAspectFill(CGRectMake(CGFloat(colNr)*w,CGFloat(rowNr)*hForColumn[colNr],w,hForColumn[colNr]))
            if i == nrofColumns || ((i % nrofColumns) == 0 && i > nrofColumns) {
                ++rowNr
                colNr = 0
            }
            else {
                ++colNr
            }
        }
    
        let outputImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return outputImage
    }
    
    extension UIImage {
        func drawInRectAspectFill(rect: CGRect, opacity: CGFloat = 1.0) {
            let targetSize = rect.size
            let scaledImage: UIImage
            if targetSize == CGSizeZero {
                scaledImage = self
            } else {
                let scalingFactor = targetSize.width / self.size.width > targetSize.height / self.size.height ? targetSize.width / self.size.width : targetSize.height / self.size.height
                let newSize = CGSize(width: self.size.width * scalingFactor, height: self.size.height * scalingFactor)
                UIGraphicsBeginImageContext(targetSize)
                self.drawInRect(CGRect(origin: CGPoint(x: (targetSize.width - newSize.width) / 2, y: (targetSize.height - newSize.height) / 2), size: newSize), blendMode: CGBlendMode.Normal, alpha: opacity)
                scaledImage = UIGraphicsGetImageFromCurrentImageContext()
                UIGraphicsEndImageContext()
            }
            scaledImage.drawInRect(rect)
        }
    }