问题
我正在建立一个拼贴的照片,从一个数组的图像,我要放在一个桌面视图。当图像数量达到tableview单元格宽度的边界时,我希望将图像包装起来(这将允许我在拼贴纸中显示图像行)。目前我只得到一排。如果需要更多的信息,请随时告知。我很可能不会以最有效的方式来处理这个问题,因为当数组中使用的图像数量开始增加时,就会出现延迟。(如对此有任何反馈,将不胜感激)。
Nota Bene
我正在创造一个拼贴的形象。它实际上是一幅图像。我希望通过在内存中创建列和行的有效矩阵来安排拼贴。然后我用图像填充这些区域。最后,我对结果图像进行快照,并在需要时使用它。该算法在编写时效率不高,只生成一行图像。我需要一个轻量级的替代算法下面使用的算法。在这种情况下,我不相信UICollectionView是一个有用的替代方案。
伪码
使用:SWIFT2.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单元格的高效均匀分布将是最佳的。

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对于实现上述效果是有用的。因此,一个更有效的编码算法是我正在寻找的。
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时,解决方案会变得越来越慢。
发布于 2015-07-28 17:10:53
为了在内存中构建拼贴,并尽可能高效,我建议查看核心图像。您可以组合多个CIFilters来创建输出映像。
您可以将CIAffineTransform过滤器应用到每个映像中,以便将它们排列起来(如果需要,可以将它们裁剪为与CICrop的大小),然后使用CISourceOverCompositing过滤器将它们组合起来。在要求输出之前,Core不会处理任何东西;而且由于所有这些都发生在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
}您可能会发现您的CIImage旋转不正确。您可以使用如下代码对其进行更正:
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()发布于 2015-07-22 18:06:41
由于您使用的是SWIFT2.0:UIStackView完成了您要手动执行的任务,并且比UICollectionView要容易得多。假设您使用的是故事板,那么创建一个带有多个嵌套TableViewCell的原型UIStackViews应该完全符合您的要求。您只需要确保插入的UIImages是相同的纵横比,如果这是您想要的。
你的算法效率很低,因为它必须用多重核心动画变换重新绘制每一幅图像,无论何时你从数组中添加或删除一幅图像。UIStackView支持动态添加和删除对象。
如果出于某种原因,仍然需要将生成的拼贴器快照为UIImage,您仍然可以在UIStackView上这样做。。
发布于 2016-01-15 21:17:02
基于上面Tommie C提供的备选方案2,我创建了一个函数
以下是功能:
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:
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)
}
}https://stackoverflow.com/questions/31510330
复制相似问题