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