【问题标题】:How to capture UIView to UIImage without loss of quality on retina display如何在不损失视网膜显示质量的情况下将 UIView 捕获到 UIImage
【发布时间】:2011-05-19 01:36:36
【问题描述】:

我的代码在普通设备上运行良好,但在视网膜设备上创建模糊图像。

有人知道我的问题的解决方案吗?

+ (UIImage *) imageWithView:(UIView *)view
{
    UIGraphicsBeginImageContext(view.bounds.size);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}

【问题讨论】:

  • 模糊。在我看来,正确的比例丢失了......
  • 我也是。遇到了同样的问题。
  • 你在哪里显示结果图像?正如其他人所解释的那样,我相信您正在将视图渲染到比例为 1 的图像上,而您的设备的比例为 2 或 3。所以您正在缩小到较低的分辨率。这是质量损失,但不应模糊。但是,当您在比例为 2 的设备上再次查看此图像(在同一屏幕上?)时,它将从低分辨率转换为更高分辨率,在屏幕截图中使用每个像素 2 个像素。这种放大最有可能使用插值(iOS 上的默认值),对额外像素的颜色值进行平均。

标签: uiimage uikit scale retina-display image-capture


【解决方案1】:

对于 Swift 5.1,您可以使用此扩展:

extension UIView {

    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)

        return renderer.image {
            rendererContext in

            layer.render(in: rendererContext.cgContext)
        }
    }
}

【讨论】:

    【解决方案2】:

    这是基于@Dima 的回答的 Swift 4 UIView 扩展

    extension UIView {
       func snapshotImage() -> UIImage? {
           UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
           drawHierarchy(in: bounds, afterScreenUpdates: false)
           let image = UIGraphicsGetImageFromCurrentImageContext()
           UIGraphicsEndImageContext()
           return image
       }
    }
    

    【讨论】:

      【解决方案3】:

      当前接受的答案现在已经过时了,至少如果您支持 iOS 7。

      如果您只支持 iOS7+,则应该使用以下内容:

      + (UIImage *) imageWithView:(UIView *)view
      {
          UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0f);
          [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
          UIImage * snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
          UIGraphicsEndImageContext();
          return snapshotImage;
      }
      

      斯威夫特 4:

      func imageWithView(view: UIView) -> UIImage? {
          UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
          defer { UIGraphicsEndImageContext() }
          view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
          return UIGraphicsGetImageFromCurrentImageContext()
      }
      

      根据this article,可以看到iOS7新方法drawViewHierarchyInRect:afterScreenUpdates:renderInContext:快很多倍。

      【讨论】:

      • 毫无疑问,drawViewHierarchyInRect:afterScreenUpdates: 要快得多。我刚刚在 Instruments 中运行了一个时间分析器。我的图像生成时间从 138 毫秒变为 27 毫秒。
      • 由于某种原因,当我尝试为 Google 地图标记创建自定义图标时,这对我不起作用;我得到的只是黑色矩形:(
      • @CarlosP 你试过将afterScreenUpdates: 设置为YES吗?
      • 将 afterScreenUpdates 设置为 YES 为我修复了黑色矩形问题
      • 我也收到了黑色图像。事实证明,如果我调用viewDidLoadviewWillAppear: 中的代码,图像是黑色的;我必须在viewDidAppear: 中进行操作。于是我也终于回复到renderInContext:
      【解决方案4】:

      UIGraphicsImageRenderer 是一个相对较新的 API,在 iOS 10 中引入。您通过指定点大小来构造 UIGraphicsImageRenderer。 image 方法接受一个闭包参数并返回一个位图,该位图是执行传递的闭包的结果。在这种情况下,结果是按比例缩小以在指定范围内绘制的原始图像。

      https://nshipster.com/image-resizing/

      所以请确保您传递给UIGraphicsImageRenderer 的大小是点,而不是像素。

      如果您的图片比您预期的要大,您需要将您的尺寸除以比例因子。

      【讨论】:

      • 我很好奇,你能帮我解决这个问题吗? stackoverflow.com/questions/61247577/… 我正在使用 UIGraphicsImageRenderer,问题是我希望导出的图像比设备上的实际视图更大。但是对于想要的尺寸,子视图(标签)不够清晰——所以会损失质量。
      【解决方案5】:

      从使用UIGraphicsBeginImageContext 切换到UIGraphicsBeginImageContextWithOptions(如文档中的on this page)。为比例(第三个参数)传递 0.0,您将获得一个比例因子等于屏幕比例因子的上下文。

      UIGraphicsBeginImageContext 使用 1.0 的固定比例因子,因此您实际上在 iPhone 4 上获得的图像与在其他 iPhone 上完全相同。我敢打赌,要么 iPhone 4 在你隐式放大时应用了滤镜,要么只是你的大脑发现它不如周围的一切清晰。

      所以,我猜:

      #import <QuartzCore/QuartzCore.h>
      
      + (UIImage *)imageWithView:(UIView *)view
      {
          UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0);
          [view.layer renderInContext:UIGraphicsGetCurrentContext()];
      
          UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
      
          UIGraphicsEndImageContext();
      
          return img;
      }
      

      在 Swift 4 中:

      func image(with view: UIView) -> UIImage? {
          UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
          defer { UIGraphicsEndImageContext() }
          if let context = UIGraphicsGetCurrentContext() {
              view.layer.render(in: context)
              let image = UIGraphicsGetImageFromCurrentImageContext()
              return image
          }
          return nil
      }
      

      【讨论】:

      • Tommy 的回答很好,但是你仍然需要导入#import &lt;QuartzCore/QuartzCore.h&gt; 来删除renderInContext: 警告。
      • @WayneLiu 您可以明确指定 2.0 的比例因子,但您可能无法获得准确的 iPhone 4 质量图像,因为已加载的任何位图都是标准版本而不是 @2x 版本.我认为没有解决方案,因为没有办法指示当前持有UIImage 的每个人重新加载,而是强制使用高分辨率版本(并且由于 RAM 较小,您可能会遇到问题无论如何都可以在视网膜前设备中使用)。
      • 不使用0.0f 作为比例参数,使用[[UIScreen mainScreen] scale] 是否更容易接受,它也可以。
      • @Adam Carter scale: The scale factor to apply to the bitmap. If you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen. 已明确记录,因此我认为 0.0f 更简单更好。
      • 此答案对于 iOS 7 已过时。请参阅我的答案以了解执行此操作的新“最佳”方法。
      【解决方案6】:

      我已经创建了一个基于@Dima 解决方案的 Swift 扩展:

      extension UIImage {
          class func imageWithView(view: UIView) -> UIImage {
              UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0)
              view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
              let img = UIGraphicsGetImageFromCurrentImageContext()
              UIGraphicsEndImageContext()
              return img
          }
      }
      

      编辑:Swift 4 改进版

      extension UIImage {
          class func imageWithView(_ view: UIView) -> UIImage {
              UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0)
              defer { UIGraphicsEndImageContext() }
              view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
              return UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
          }
      }
      

      用法:

      let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))  
      let image = UIImage.imageWithView(view)
      

      【讨论】:

      • 感谢您的建议!顺便说一句,您也可以在开始上下文后立即推迟 { UIGraphicsEndImageContext() } 并避免引入局部变量 img ;)
      • 非常感谢详细的解释和使用!
      • 为什么这是一个class 的函数来获取视图?
      • 这就像 static 函数一样工作,但由于不需要覆盖,您可以简单地将其更改为 static 函数而不是 class
      【解决方案7】:

      支持新 iOS 10.0 API 和之前方法的 Swift 3.0 插件。

      注意:

      • iOS 版本检查
      • 注意使用 defer 来简化上下文清理。
      • 还将应用视图的不透明度和当前比例。
      • 使用! 解包不会导致崩溃。

      extension UIView
      {
          public func renderToImage(afterScreenUpdates: Bool = false) -> UIImage?
          {
              if #available(iOS 10.0, *)
              {
                  let rendererFormat = UIGraphicsImageRendererFormat.default()
                  rendererFormat.scale = self.layer.contentsScale
                  rendererFormat.opaque = self.isOpaque
                  let renderer = UIGraphicsImageRenderer(size: self.bounds.size, format: rendererFormat)
      
                  return
                      renderer.image
                      {
                          _ in
      
                          self.drawHierarchy(in: self.bounds, afterScreenUpdates: afterScreenUpdates)
                      }
              }
              else
              {
                  UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, self.layer.contentsScale)
                  defer
                  {
                      UIGraphicsEndImageContext()
                  }
      
                  self.drawHierarchy(in: self.bounds, afterScreenUpdates: afterScreenUpdates)
      
                  return UIGraphicsGetImageFromCurrentImageContext()
              }
          }
      }
      

      【讨论】:

        【解决方案8】:

        所有 Swift 3 答案都对我不起作用,所以我翻译了最被接受的答案:

        extension UIImage {
            class func imageWithView(view: UIView) -> UIImage {
                UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
                view.layer.render(in: UIGraphicsGetCurrentContext()!)
                let img: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
                UIGraphicsEndImageContext()
                return img!
            }
        }
        

        【讨论】:

          【解决方案9】:

          斯威夫特 3

          带有 UIView 扩展的 Swift 3 解决方案(基于Dima's answer)应该是这样的:

          extension UIView {
              public func getSnapshotImage() -> UIImage {
                  UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0)
                  self.drawHierarchy(in: self.bounds, afterScreenUpdates: false)
                  let snapshotImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
                  UIGraphicsEndImageContext()
                  return snapshotImage
              }
          }
          

          【讨论】:

            【解决方案10】:

            使用现代 UIGraphicsImageRenderer

            public extension UIView {
                @available(iOS 10.0, *)
                public func renderToImage(afterScreenUpdates: Bool = false) -> UIImage {
                    let rendererFormat = UIGraphicsImageRendererFormat.default()
                    rendererFormat.opaque = isOpaque
                    let renderer = UIGraphicsImageRenderer(size: bounds.size, format: rendererFormat)
            
                    let snapshotImage = renderer.image { _ in
                        drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
                    }
                    return snapshotImage
                }
            }
            

            【讨论】:

            • @masaldana2 可能还没有,但一旦他们开始弃用东西或添加硬件加速渲染或改进,你最好站在巨人的肩膀上(AKA 使用最后的 Apple API)
            • 有人知道这个renderer 与旧的drawHierarchy.. 相比的性能如何吗?
            【解决方案11】:

            Swift 3.0 实现

            extension UIView {
                func getSnapshotImage() -> UIImage {
                    UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
                    drawHierarchy(in: bounds, afterScreenUpdates: false)
                    let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()!
                    UIGraphicsEndImageContext()
                    return snapshotImage
                }
            }
            

            【讨论】:

              【解决方案12】:

              斯威夫特 2.0:

              使用扩展方法:

              extension UIImage{
              
                 class func renderUIViewToImage(viewToBeRendered:UIView?) -> UIImage
                 {
                     UIGraphicsBeginImageContextWithOptions((viewToBeRendered?.bounds.size)!, false, 0.0)
                     viewToBeRendered!.drawViewHierarchyInRect(viewToBeRendered!.bounds, afterScreenUpdates: true)
                     viewToBeRendered!.layer.renderInContext(UIGraphicsGetCurrentContext()!)
              
                     let finalImage = UIGraphicsGetImageFromCurrentImageContext()
                     UIGraphicsEndImageContext()
              
                     return finalImage
                 }
              
              }
              

              用法:

              override func viewDidLoad() {
                  super.viewDidLoad()
              
                  //Sample View To Self.view
                  let sampleView = UIView(frame: CGRectMake(100,100,200,200))
                  sampleView.backgroundColor =  UIColor(patternImage: UIImage(named: "ic_120x120")!)
                  self.view.addSubview(sampleView)    
              
                  //ImageView With Image
                  let sampleImageView = UIImageView(frame: CGRectMake(100,400,200,200))
              
                  //sampleView is rendered to sampleImage
                  var sampleImage = UIImage.renderUIViewToImage(sampleView)
              
                  sampleImageView.image = sampleImage
                  self.view.addSubview(sampleImageView)
              
               }
              

              【讨论】:

                【解决方案13】:

                在这个方法中只传递一个视图对象,它会返回一个 UIImage 对象。

                -(UIImage*)getUIImageFromView:(UIView*)yourView
                {
                 UIGraphicsBeginImageContext(yourView.bounds.size);
                 [yourView.layer renderInContext:UIGraphicsGetCurrentContext()];
                 UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
                 UIGraphicsEndImageContext();
                 return image;
                }
                

                【讨论】:

                  【解决方案14】:
                  - (UIImage*)screenshotForView:(UIView *)view
                  {
                      UIGraphicsBeginImageContext(view.bounds.size);
                      [view.layer renderInContext:UIGraphicsGetCurrentContext()];
                      UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
                      UIGraphicsEndImageContext();
                  
                      // hack, helps w/ our colors when blurring
                      NSData *imageData = UIImageJPEGRepresentation(image, 1); // convert to jpeg
                      image = [UIImage imageWithData:imageData];
                  
                      return image;
                  }
                  

                  【讨论】:

                    【解决方案15】:

                    要改进@Tommy 和@Dima 的答案,请使用以下类别将UIView 渲染为具有透明背景 且不损失质量的UIImage。在 iOS7 上工作。 (或者只是在实现中重用该方法,将 self 引用替换为您的图像)

                    UIView+RenderViewToImage.h

                    #import <UIKit/UIKit.h>
                    
                    @interface UIView (RenderToImage)
                    
                    - (UIImage *)imageByRenderingView;
                    
                    @end
                    

                    UIView+RenderViewToImage.m

                    #import "UIView+RenderViewToImage.h"
                    
                    @implementation UIView (RenderViewToImage)
                    
                    - (UIImage *)imageByRenderingView
                    {
                        UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
                        [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
                        UIImage * snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
                        UIGraphicsEndImageContext();
                        return snapshotImage;
                    }
                    
                    @end
                    

                    【讨论】:

                    • 如果我将 drawViewHierarchyInRect 与 afterScreenUpdates:YES 一起使用,我的图像纵横比已经改变并且图像失真。
                    • 当你的 uiview 内容改变时会发生这种情况吗?如果是,则尝试再次重新生成此 UIImage。抱歉,我在打电话,所以无法自己测试
                    • 我也有同样的问题,好像没有人注意到这个问题,你找到解决这个问题的方法了吗? @配置文件?
                    • 这正是我所需要的,但它没有用。我尝试将@interface@implementation 都设为(RenderViewToImage) 并将#import "UIView+RenderViewToImage.h" 添加到ViewController.h 的标题中,这是正确的用法吗?例如UIImageView *test = [[UIImageView alloc] initWithImage:[self imageByRenderingView]]; [self addSubview:test]; ?
                    • ViewController.m 中的这个方法是我试图渲染的视图- (UIView *)testView { UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 50, 50)]; v1.backgroundColor = [UIColor redColor]; UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 150, 150)]; v2.backgroundColor = [UIColor blueColor]; [v2 addSubview:v1]; return v2; }
                    【解决方案16】:

                    有时 drawRect 方法会产生问题,所以我得到这些答案更合适。你也可以看看 Capture UIImage of UIView stuck in DrawRect method

                    【讨论】:

                      【解决方案17】:

                      将此添加到 UIView 类别的方法

                      - (UIImage*) capture {
                          UIGraphicsBeginImageContext(self.bounds.size);
                          CGContextRef context = UIGraphicsGetCurrentContext();
                          [self.layer renderInContext:context];
                          UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
                          UIGraphicsEndImageContext();
                          return img;
                      }
                      

                      【讨论】:

                      • 您好,虽然这可能很好地回答了这个问题,但请注意,其他用户可能不像您那样知识渊博。您为什么不添加一些关于此代码为何有效的解释?谢谢!
                      猜你喜欢
                      • 1970-01-01
                      • 2015-10-10
                      • 2017-03-24
                      • 2019-10-26
                      • 1970-01-01
                      • 2016-01-27
                      • 2015-01-26
                      • 1970-01-01
                      相关资源
                      最近更新 更多