将多张图片垂直拼接并保存为一张图片(iOS,objective-c)

11
我需要帮忙编写一个Objective-C函数,该函数将接收一组UIImages / PNGs的数组,并返回/保存所有图像垂直拼接在一起的一张高图像。 我是新手,请慢慢来。
我的初步想法是: 绘制一个UIView,然后将每个图像的父视图添加为addSubviews, 然后???

你能把我的答案设为这个问题的被接受答案吗?这样方便其他人快速找到最佳答案(例如,实际解决问题并提供工作示例的答案)。这可能看起来像一个奇怪或贪婪的要求,但它不是。 “被接受的答案”的目标是尽可能直接地引导人们找到最实用的可行解决方案,这就是为什么允许您随着解决方案的增加而更改它的原因。 - clearlight
6个回答

22

以下是将图像垂直或水平拼接在一起的Swift 3和Swift 2示例。它们使用由调用者提供的数组中最大图像的尺寸来确定用于每个单独帧的公共大小,将每个单独图像拼接到其中。

注意:Swift 3示例保留了每个图像的纵横比,而Swift 2示例则没有。请参见下面有关此问题的注释。

更新:添加了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,并希望保留纵横比,请使用来自Swift 3示例的AVMakeRect()修饰。由于我不再能够访问Swift 2 playground并且无法测试以确保没有错误,所以我不会在这里更新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
}

@Happiehappie 完成了!在使用不全是正方形的图像进行Swift Playground测试时,我注意到在Swift 2示例中图像被水平或垂直拉伸而没有保持纵横比。我已经在新的Swift 3示例中修复了这个问题。谢谢! - clearlight

9
常规方法是创建一个位图图像上下文,将所需位置的图像进行绘制,然后从图像上下文获取图像。
您可以使用UIKit来完成此操作,这样会更加简单,但是不是线程安全的,因此需要在主线程中运行,并且可能会阻塞UI界面。
有大量的示例代码可用于此操作,但是如果要正确理解它,应该查看UIGraphicsContextBeginImageContext、UIGraphicsGetCurrentContext、UIGraphicsGetImageFromCurrentImageContext和UIImageDrawInRect等函数。不要忘记使用UIGraphicsPopCurrentContext函数。
您也可以使用Core Graphics完成此操作,它应该是线程安全的(到目前为止我还没有从中崩溃过)。效率大致相同,因为UIKit只是在底层使用CG。
此操作的关键字是CGBitmapContextCreate、CGContextDrawImage、CGBitmapContextCreateImage、CGContextTranslateCTM、CGContextScaleCTM和CGContextRelease(没有ARC用于Core Graphics)。缩放和翻译是因为CG的原点位于右下角,Y朝上增加。
还有第三种方法,可以使用CG用于上下文,但通过使用CALayer来保存所有坐标转换的痛苦,将CGImage(UIImage.CGImage)设置为内容,然后将该图层渲染到上下文中。这仍然是线程安全的,并且让图层处理所有转换。关键字为- renderInContext:

谢谢你,我现在就开始研究这个。 - Cameron E

6

我知道我来晚了,但希望这可以帮助到某些人。如果你想从一个数组中创建一个大图像,你可以使用以下方法:

- (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;
}

1
公平地说,你的评论也没有帮助到我,而我只是想要提供帮助。 - What what

5

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)
            }
        }
    }
}

太棒了,Tori!我喜欢它是一个扩展程序,感谢您的发布。 - Chris
谢谢Tori!这个解决方案不仅有效,而且效果很好。在从UIGraphicsGetImageFromCurrentImageContext切换到UIGraphicsImageRenderer后,我能够在将超过10个全屏图像拼接成一张长图时保持图像质量。这应该是被采纳的答案! - Oliver Zhang

2

尝试使用这段代码,我尝试将两个图像拼接在一起并在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是ImageView的名称,您可以将多张图片合并成一张单独的图片进行查看。


2
上述解决方案虽然有用,但对我来说存在一个严重的缺陷。问题在于,如果图像的大小不同,则拼接后的图像可能会在各部分之间存在很大的空隙。我提出的解决方案是将所有图像紧密地排列在一起,这样无论个别图像的大小如何,它们看起来更像是单张图像。 适用于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!
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接