Как эффективно создать многорядный фотоколлаж из массива изображений в Swift
Я создаю коллаж фотографий из массива изображений, которые я размещаю на tableview. Я хочу, чтобы изображения обернулись, когда количество изображений достигнет границы ширины ячейки 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 было бы оптимальным.
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. Меня по-прежнему интересует любой алгоритм, который может обеспечить ту же функциональность только быстрее.
Nota Bene: Я не считаю, что добавление большего 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 }
Эффективность Тестирования
Реда Лемеден дает некоторое процедурное представление о том, как проверить эти вызовы CG в инструментах на этом блоге. Он также указывает на некоторые интересные заметки Энди Матушака (из команды UIKit) о некоторых особенностях закадрового перевода. Я, вероятно, все еще не использую решение CIImage должным образом, потому что начальные результаты показывают, что решение становится медленнее при попытке принудительного использования GPU.
4 ответов
чтобы построить коллаж в памяти и быть максимально эффективным, я бы предложил заглянуть в Core Image. Вы можете объединить несколько CIFilters для создания выходного изображения.
вы можете подать заявку CIAffineTransform фильтры для каждого из ваших изображений, чтобы выровнять их (обрезка их по размеру с CICrop при необходимости), затем объедините их с помощью CISourceOverCompositing фильтры. Core Image ничего не обрабатывает, пока вы спросите о выходе; и поскольку все это происходит в GPU, это быстро и эффективно.
вот немного кода. Я старался держать его как можно ближе к вашему примеру, чтобы понять. Это не обязательно то, как я бы структурировал код, если бы я использовал core image с нуля.
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()
поскольку вы используете Swift 2.0:UIStackView
делает именно то, что вы пытаетесь сделать вручную, и значительно проще в использовании, чем UICollectionView. Предполагая, что вы используете раскадровки, создание прототипа TableViewCell с несколькими вложенными UIStackViews должно делать именно то, что вы хотите. Вам просто нужно убедиться, что uiimages, которые вы вставляете, имеют одинаковое соотношение сторон, если это то, что вы хотите.
ваш алгоритм очень неэффективен, потому что он имеет для повторного рисования, с несколькими основными преобразованиями анимации,каждое изображение каждый раз, когда вы добавляете или удаляете изображение из массива. UIStackView
поддерживает динамическое добавление и удаление объектов.
Если вам все еще по какой-то причине нужно сделать снимок полученного коллажа как UIImage,вы все еще можете сделать это на UIStackView.
строительство на альтернативе 2, предоставленной Томми C выше я создал функцию, которая
- всегда заполняет общий прямоугольник, без пробелов в коллаже
- определяет количество строк и столбцов автоматически (максимум 1 больше nrOfColumns, чем nrOfRows)
- чтобы предотвратить пробелы, упомянутые выше, все отдельные фотографии рисуются с помощью "заливки аспекта" (поэтому для некоторых фотографий это означает, что части будут обрезано)
вот функция:
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)
}
}
Если кто-то ищет код Objective C, это хранилище может быть полезно.