首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何有效地从Swift中的图像数组创建多行照片拼贴

如何有效地从Swift中的图像数组创建多行照片拼贴
EN

Stack Overflow用户
提问于 2015-07-20 06:52:49
回答 4查看 6.3K关注 0票数 13

问题

我正在建立一个拼贴的照片,从一个数组的图像,我要放在一个桌面视图。当图像数量达到tableview单元格宽度的边界时,我希望将图像包装起来(这将允许我在拼贴纸中显示图像行)。目前我只得到一排。如果需要更多的信息,请随时告知。我很可能不会以最有效的方式来处理这个问题,因为当数组中使用的图像数量开始增加时,就会出现延迟。(如对此有任何反馈,将不胜感激)。

Nota Bene

我正在创造一个拼贴的形象。它实际上是一幅图像。我希望通过在内存中创建列和行的有效矩阵来安排拼贴。然后我用图像填充这些区域。最后,我对结果图像进行快照,并在需要时使用它。该算法在编写时效率不高,只生成一行图像。我需要一个轻量级的替代算法下面使用的算法。在这种情况下,我不相信UICollectionView是一个有用的替代方案。

伪码

  1. 给定图像数组和目标矩形(表示目标视图)
  2. 获取数组中的图像数,与每行允许的最大数目相比。
  3. 定义一个适当大小的小矩形来容纳图像(这样每一行都填充目标矩形,也就是说-如果有一幅图像,那就应该填充这一行;如果9幅图像,那就应该完全填充这一行;如果10幅图像的最大值为每行9幅图像,那么第10行就开始了第二行)
  4. 遍历集合
  5. 将每个矩形从左到右放置在正确的位置,直到最后一个图像或每一行的最大数字达到;在下一行继续,直到所有图像都适合目标矩形。
  6. 当每行图像数达到最大时,将图像放置并设置下一个矩形以显示在连续行上。

使用:SWIFT2.0

代码语言:javascript
复制
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单元格的高效均匀分布将是最佳的。

代码语言:javascript
复制
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类上扩展的一个简单类函数中。我仍然感兴趣的任何算法,可以提供相同的功能,只是更快。

noted :我不认为添加更多的UI对于实现上述效果是有用的。因此,一个更有效的编码算法是我正在寻找的。

代码语言:javascript
复制
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给出了一些关于如何在此博客帖子上测试仪器中的CG调用的过程性见解。他还指出了UIKit团队的Andy关于离屏渲染的一些特点的有趣的笔记。我可能仍然没有正确地利用CIImage解决方案,因为最初的结果显示,当试图强制使用GPU时,解决方案会变得越来越慢。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2015-07-28 17:10:53

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

您可以将CIAffineTransform过滤器应用到每个映像中,以便将它们排列起来(如果需要,可以将它们裁剪为与CICrop的大小),然后使用CISourceOverCompositing过滤器将它们组合起来。在要求输出之前,Core不会处理任何东西;而且由于所有这些都发生在GPU中,所以它速度快、效率高。

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

代码语言:javascript
复制
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旋转不正确。您可以使用如下代码对其进行更正:

代码语言:javascript
复制
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。

代码语言:javascript
复制
// 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()
票数 14
EN

Stack Overflow用户

发布于 2015-07-22 18:06:41

由于您使用的是SWIFT2.0:UIStackView完成了您要手动执行的任务,并且比UICollectionView要容易得多。假设您使用的是故事板,那么创建一个带有多个嵌套TableViewCell的原型UIStackViews应该完全符合您的要求。您只需要确保插入的UIImages是相同的纵横比,如果这是您想要的。

你的算法效率很低,因为它必须用多重核心动画变换重新绘制每一幅图像,无论何时你从数组中添加或删除一幅图像。UIStackView支持动态添加和删除对象。

如果出于某种原因,仍然需要将生成的拼贴器快照为UIImage,您仍然可以在UIStackView上这样做。

票数 4
EN

Stack Overflow用户

发布于 2016-01-15 21:17:02

基于上面Tommie C提供的备选方案2,我创建了一个函数

  • 总是填充整个矩形,在拼贴图中没有空格。
  • 自动确定行数和列数(最多比nrOfRows多1行)
  • 为了防止上面提到的空格,所有单独的图片都用“方面填充”绘制(因此对于一些图片,这意味着部分将被裁剪)。

以下是功能:

代码语言:javascript
复制
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
}

这使用了UIImage扩展drawInRectAspectFill:

代码语言:javascript
复制
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)
    }
}
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/31510330

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档