【问题标题】:Stitch/Composite multiple images vertically and save as one image (iOS, objective-c)垂直拼接/合成多张图像并保存为一张图像(iOS,objective-c)
【发布时间】:2012-07-29 23:22:01
【问题描述】:

我需要帮助编写一个objective-c 函数,该函数将接收一组UIImages/PNG,并返回/保存所有垂直拼接在一起的图像的一个高图像。我是新手,所以请慢慢来,放轻松:)

到目前为止我的想法: 绘制一个 UIView,然后将Subviews 添加到每个人的父级(图像) 然后???

【问题讨论】:

  • 您能否对这个问题做出公认的答案,以便人们更容易快速找到最佳答案(例如,实际解决问题并提供工作示例的答案)?这似乎是一个奇怪或贪婪的请求,但事实并非如此。 “接受的答案”的目标是尽可能直接地引导人们找到最实际可行的解决方案,这就是为什么您可以在解决方案增加时对其进行更改。

标签: objective-c ios xcode image


【解决方案1】:

通常的方法是创建一个位图图像上下文,在需要的位置绘制图像,然后从图像上下文中获取图像。

您可以使用 UIKit 来执行此操作,这有点容易,但不是线程安全的,因此需要在主线程中运行并会阻塞 UI。

这方面有很多示例代码,但如果您想正确理解它,您应该查看 UIGraphicsContextBeginImageContext、UIGraphicsGetCurrentContext、UIGraphicsGetImageFromCurrentImageContext 和 UIImageDrawInRect。不要忘记 UIGraphicsPopCurrentContext。

您也可以使用 AFAIK 的 Core Graphics 来执行此操作,可以安全地在后台线程上使用(我还没有因它而崩溃)。效率大致相同,因为 UIKit 只是在后台使用 CG。 关键字是 CGBitmapContextCreate、CGContextDrawImage、CGBitmapContextCreateImage、CGContextTranslateCTM、CGContextScaleCTM 和 CGContextRelease(核心图形没有 ARC)。缩放和平移是因为CG的原点在右下角,Y向上增加。

还有第三种方式,就是用CG做context,但是用CALayer省去坐标的痛苦,把你的CGImage(UIImage.CGImage)设置为contents,然后把图层渲染成上下文。这仍然是线程安全的,并让层负责所有的转换。关键字是 -renderInContext:

【讨论】:

  • 谢谢你,我现在就开始研究这个。
【解决方案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 是图像视图的名称,您可以将图像视为单个图像。

【讨论】:

    【解决方案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】:

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

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

    更新:添加了 Swift 3 示例

    斯威夫特 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
    }
    

    【讨论】:

    • @Happiehappie 完成!在 Swift Playground 中使用并非全部为正方形的图像进行测试时,我注意到图像在水平或垂直方向上被拉伸,没有保留 Swift 2 示例中的纵横比。我已经在添加的新 Swift 3 示例中解决了这个问题。谢谢!
    【解决方案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】:

      斯威夫特 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)
                  }
              }
          }
      }
      

      【讨论】:

      • 不错的一个托里!我喜欢它的扩展,感谢您发布此内容。
      • 谢谢托里!该解决方案不仅有效,而且效果很好。从 UIGraphicsGetImageFromCurrentImageContext 切换到 UIGraphicsImageRenderer 后,我能够在将 10 多张全屏图像拼接成一张长图时保持图像质量。这应该是公认的答案!
      猜你喜欢
      • 2013-07-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-11
      • 2022-07-01
      相关资源
      最近更新 更多