Stitch/Composite несколько изображений по вертикали и сохранить как одно изображение (iOS, objective-c)

Мне нужна помощь, записывающая функцию objective-c, которая возьмет в массив UIImages/PNG, и вернет/сохранит одно высокое изображение всех изображений, сшитых вместе по вертикали. Я новичок в этом, поэтому, пожалуйста, пройдите медленнее и успокойтесь:)

Мои идеи до сих пор: Нарисуйте UIView, затем addSubviews для каждого родителя (из изображений) и затем

Ответ 1

Обычным способом является создание контекста растрового изображения, рисование изображений в нужном месте, а затем получение изображения из контекста изображения.

Вы можете сделать это с помощью UIKit, который несколько проще, но не является потокобезопасным, поэтому его нужно будет запустить в основном потоке и заблокировать пользовательский интерфейс.

Для этого есть множество примеров кода, но если вы хотите правильно его понять, вы должны посмотреть на UIGraphicsContextBeginImageContext, UIGraphicsGetCurrentContext, UIGraphicsGetImageFromCurrentImageContext и UIImageDrawInRect. Не забудьте UIGraphicsPopCurrentContext.

Вы также можете сделать это с помощью Core Graphics, которая является AFAIK, безопасной для использования в фоновом потоке (у меня еще не было сбоя от нее). Эффективность примерно такая же, как UIKit, просто использует CG под капотом. Ключевые слова для этого: CGBitmapContextCreate, CGContextDrawImage, CGBitmapContextCreateImage, CGContextTranslateCTM, CGContextScaleCTM и CGContextRelease (без ARC для базовой графики). Масштабирование и перевод - это потому, что CG имеет начало в нижнем правом углу, а Y - вверх.

Существует также третий способ, который заключается в использовании CG для контекста, но сохраняйте все скоординированные боли с помощью CALayer, установите CGImage (UIImage.CGImage) в качестве содержимого, а затем визуализируйте слой контекст. Это по-прежнему является потокобезопасным и позволяет слою заботиться обо всех преобразованиях. Ключевые слова для этого - renderInContext:

Ответ 2

Ниже приведены примеры Swift 3 и Swift 2, которые строчат изображения по вертикали или по горизонтали. Они используют размеры самого большого изображения в массиве, предоставленные вызывающим, для определения общего размера, используемого для каждого отдельного кадра, в которое сшивается каждое отдельное изображение.

Примечание. Пример Swift 3 сохраняет все пропорции изображения, а пример Swift 2 - нет. См. Примечание ниже в отношении этого.

UPDATE: добавлен пример Swift 3

Swift 3:

import UIKit
import AVFoundation

func stitchImages(images: [UIImage], isVertical: Bool) -> UIImage {
    var stitchedImages : UIImage!
    if images.count > 0 {
        var maxWidth = CGFloat(0), maxHeight = CGFloat(0)
        for image in images {
            if image.size.width > maxWidth {
                maxWidth = image.size.width
            }
            if image.size.height > maxHeight {
                maxHeight = image.size.height
            }
        }
        var totalSize : CGSize
        let maxSize = CGSize(width: maxWidth, height: maxHeight)
        if isVertical {
            totalSize = CGSize(width: maxSize.width, height: maxSize.height * (CGFloat)(images.count))
        } else {
            totalSize = CGSize(width: maxSize.width  * (CGFloat)(images.count), height:  maxSize.height)
        }
        UIGraphicsBeginImageContext(totalSize)
        for image in images {
            let offset = (CGFloat)(images.index(of: image)!)
            let rect =  AVMakeRect(aspectRatio: image.size, insideRect: isVertical ?
                            CGRect(x: 0, y: maxSize.height * offset, width: maxSize.width, height: maxSize.height) :
                            CGRect(x: maxSize.width * offset, y: 0, width: maxSize.width, height: maxSize.height))
            image.draw(in: rect)
        }
        stitchedImages = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
    }
    return stitchedImages
}

Примечание. Приведенный ниже пример Swift 2 не сохраняет (например, в примере Swift 2 все изображения расширены до поместится в ограничительную рамку, которая представляет экстремумы ширины и высоты изображений, таким образом, любое неквадратное изображение может быть растянуто непропорционально в одном из его размеров). Если вы используете Swift 2 и хотите сохранить соотношение сторон, используйте AVMakeRect()модификация из примера Swift 3. Поскольку у меня больше нет доступа к игровой площадке Swift 2 и не может ее проверить, чтобы не было ошибок, я не обновив здесь пример Swift 2.

Swift 2: (не сохраняет соотношение сторон. Исправлено в примере выше, Swift 3)

import UIKit
import AVFoundation

func stitchImages(images: [UIImage], isVertical: Bool) -> UIImage {
    var stitchedImages : UIImage!
    if images.count > 0 {
        var maxWidth = CGFloat(0), maxHeight = CGFloat(0)
        for image in images {
            if image.size.width > maxWidth {
                maxWidth = image.size.width
            }
            if image.size.height > maxHeight {
                maxHeight = image.size.height
            }
        }
        var totalSize : CGSize, maxSize = CGSizeMake(maxWidth, maxHeight)
        if isVertical {
            totalSize = CGSizeMake(maxSize.width, maxSize.height * (CGFloat)(images.count))
        } else {
            totalSize = CGSizeMake(maxSize.width  * (CGFloat)(images.count), maxSize.height)
        }
        UIGraphicsBeginImageContext(totalSize)
        for image in images {
            var rect : CGRect, offset = (CGFloat)((images as NSArray).indexOfObject(image))
            if isVertical {
                rect = CGRectMake(0, maxSize.height * offset, maxSize.width, maxSize.height)
            } else {
                rect = CGRectMake(maxSize.width * offset, 0 , maxSize.width, maxSize.height)
            }
            image.drawInRect(rect)
        }
        stitchedImages = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
    }
    return stitchedImages
}

Ответ 3

Я знаю, что я немного опоздал, но, надеюсь, это может помочь кому-то. Если вы пытаетесь создать одно большое изображение из массива, вы можете использовать этот метод

- (UIImage *)mergeImagesFromArray: (NSArray *)imageArray {

    if ([imageArray count] == 0) return nil;

    UIImage *exampleImage = [imageArray firstObject];
    CGSize imageSize = exampleImage.size;
    CGSize finalSize = CGSizeMake(imageSize.width, imageSize.height * [imageArray count]);

    UIGraphicsBeginImageContext(finalSize);

    for (UIImage *image in imageArray) {
        [image drawInRect: CGRectMake(0, imageSize.height * [imageArray indexOfObject: image],
                                      imageSize.width, imageSize.height)];
    }

    UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return finalImage;
}

Ответ 4

Попробуйте этот кусок кода, я попробовал сшить два изображения вместе, n отобразил их в ImageView

UIImage *bottomImage = [UIImage imageNamed:@"bottom.png"]; //first image
UIImage *image       = [UIImage imageNamed:@"top.png"]; //foreground image

CGSize newSize = CGSizeMake(209, 260); //size of image view
UIGraphicsBeginImageContext( newSize );

// drawing 1st image
[bottomImage drawInRect:CGRectMake(0,0,newSize.width/2,newSize.height/2)];

// drawing the 2nd image after the 1st
[image drawInRect:CGRectMake(0,newSize.height/2,newSize.width/2,newSize.height/2)] ;

UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

join.image = newImage;   

join - это имя изображения, и вы можете видеть изображения как одно изображение.

Ответ 5

Вышеприведенные решения были полезны, но имели серьезный недостаток для меня. Проблема в том, что если изображения имеют разные размеры, получившееся сшитое изображение будет иметь потенциально большие промежутки между частями. Решение, которое я придумал, объединяет все изображения прямо друг под другом, так что это похоже на одно изображение независимо от размеров отдельных изображений.

Для Swift 3.x

static func combineImages(images:[UIImage]) -> UIImage
{
    var maxHeight:CGFloat = 0.0
    var maxWidth:CGFloat = 0.0

    for image in images
    {
        maxHeight += image.size.height
        if image.size.width > maxWidth
        {
            maxWidth = image.size.width
        }
    }

    let finalSize = CGSize(width: maxWidth, height: maxHeight)

    UIGraphicsBeginImageContext(finalSize)

    var runningHeight: CGFloat = 0.0

    for image in images
    {
        image.draw(in: CGRect(x: 0.0, y: runningHeight, width: image.size.width, height: image.size.height))
        runningHeight += image.size.height
    }

    let finalImage = UIGraphicsGetImageFromCurrentImageContext()

    UIGraphicsEndImageContext()

    return finalImage!
}

Ответ 6

Swift 4

extension Array where Element: UIImage {
    func stitchImages(isVertical: Bool) -> UIImage {

        let maxWidth = self.compactMap { $0.size.width }.max()
        let maxHeight = self.compactMap { $0.size.height }.max()

        let maxSize = CGSize(width: maxWidth ?? 0, height: maxHeight ?? 0)
        let totalSize = isVertical ?
            CGSize(width: maxSize.width, height: maxSize.height * (CGFloat)(self.count))
            : CGSize(width: maxSize.width  * (CGFloat)(self.count), height:  maxSize.height)
        let renderer = UIGraphicsImageRenderer(size: totalSize)

        return renderer.image { (context) in
            for (index, image) in self.enumerated() {
                let rect = AVMakeRect(aspectRatio: image.size, insideRect: isVertical ?
                    CGRect(x: 0, y: maxSize.height * CGFloat(index), width: maxSize.width, height: maxSize.height) :
                    CGRect(x: maxSize.width * CGFloat(index), y: 0, width: maxSize.width, height: maxSize.height))
                image.draw(in: rect)
            }
        }
    }
}