首页 > 解决方案 > 如何以编程方式使用 UIGraphicsImageRenderer 获取高质量的屏幕截图?

问题描述

问题:在我截取屏幕截图后,通过缩放检查时图像模糊。缩放时图像内的文本似乎模糊。

我知道这个问题已经被提出过很多次了,但他们都没有想要的解决方案。我已经检查了很多这样的帖子

到目前为止,在此论坛上共享的所有解决方案都以任何其他方式重复或相同,但没有一个解决方案可以解决该问题。

这是我正在做的事情:

extension UIView {

  func asImage() -> UIImage? {
    let format = UIGraphicsImageRendererFormat()
    format.opaque = self.isOpaque
    let renderer = UIGraphicsImageRenderer(bounds: bounds,format: format)
    return renderer.image(actions: { rendererContext in
        layer.render(in: rendererContext.cgContext)
    })
}

//The other option using UIGraphicsEndImageContext

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

上述函数会将 UIView 转换为图像,但返回的图像质量不合格。

标签: iosswiftuiimagecgcontextuigraphicsimagerenderer

解决方案


UIView通过进行“图像捕获” ,您不会得到想要的结果。

当您缩放时,UIScrollView不会执行矢量缩放......它会执行光栅化缩放。

您可以通过将 aUILabel用作来轻松确认这一点viewForZooming。这是一个带有 30 磅系统字体的标签...

1x缩放:

在此处输入图像描述

10x缩放:

在此处输入图像描述

该示例的代码:

class ViewController: UIViewController, UIScrollViewDelegate {
    
    let zoomLabel: UILabel = UILabel()
    let scrollView: UIScrollView = UIScrollView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        [zoomLabel, scrollView].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        scrollView.addSubview(zoomLabel)
        view.addSubview(scrollView)
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            scrollView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            scrollView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            scrollView.widthAnchor.constraint(equalToConstant: 300.0),
            scrollView.heightAnchor.constraint(equalToConstant: 200.0),
            
            zoomLabel.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
            zoomLabel.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
            zoomLabel.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
            zoomLabel.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
            
        ])
        
        zoomLabel.textColor = .red
        zoomLabel.backgroundColor = .yellow
        zoomLabel.font = UIFont.systemFont(ofSize: 30.0, weight: .regular)
        zoomLabel.text = "Sample Text"
        
        scrollView.delegate = self
        scrollView.minimumZoomScale = 1
        scrollView.maximumZoomScale = 10
        
        view.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        scrollView.backgroundColor = .white
    }
    

    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return zoomLabel
    }

}

当您将视图内容“捕获”为UIImage时,您将获得一个位图,该位图是视图的大小(以点x 屏幕比例为单位)。

因此,例如,在具有@2x屏幕比例的 iPhone 8 上,300 x 200视图将被“捕获”为UIImage像素600 x 400

无论您缩放视图本身,还是视图的位图捕获,您都会得到相同的结果——缩放时边缘模糊。

您的评论包括:“......在编辑图像时......” - 这是一个常见问题,我们希望允许用户添加文本(标签)、贝塞尔路径形状、添加图像等。用户看到的例如,屏幕上可能是3000 x 2000像素的原始图像,显示在300 x 200点上。添加一个 30 点的标签在屏幕上可能看起来不错,但随后将其作为一个UIImage(用于缩放或保存到磁盘),最终成为一个600 x 400像素图像,当然,在更大的尺寸下看起来并不好。

通常,解决此问题的方法如下:

允许用户在屏幕尺寸上进行编辑,例如

  • 在视图中显示3000 x 2000按比例缩小的像素图像300 x 200
  • 添加一个贝塞尔路径,椭圆形矩形(20、20、200、200)
  • 在原点添加 30 点标签(32, 32)

然后,当“捕获”输出/缩放时

  • 原始 3000 x 2000像素图像
  • 添加一个贝塞尔路径,椭圆形矩形(20 * 10, 20 * 10, 200 * 10, 200 * 10)
  • 在原点添加 (30 * 10) 点标签(32 * 10, 32 * 10)

另一种选择是按比例缩小屏幕编辑。

因此,您可以使用300 x 200图像视图和3000 x 2000像素图像(缩放以适应)。当用户说“我想在 rect 中添加一个椭圆的 Bezier 路径时(20, 20, 200, 200),您的代码将(20 * 10, 20 * 10, 200 * 10, 200 * 10) 在图像本身的 rect 处绘制该椭圆,然后刷新.image图像视图的属性。

这是一个更详细的示例,以帮助使事情变得清晰:

class ViewController: UIViewController, UIScrollViewDelegate {
    
    let topView: UIView = UIView()
    let topLabel: UILabel = UILabel()
    
    let botView: UIView = UIView()
    let botLabel: UILabel = UILabel()
    
    let topScrollView: UIScrollView = UIScrollView()
    let botScrollView: UIScrollView = UIScrollView()

    let topStatLabel: UILabel = UILabel()
    let botStatLabel: UILabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        [topView, topLabel, botView, botLabel, topScrollView, botScrollView, topStatLabel, botStatLabel].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        topView.addSubview(topLabel)
        botView.addSubview(botLabel)

        topScrollView.addSubview(topView)
        botScrollView.addSubview(botView)

        view.addSubview(topStatLabel)
        view.addSubview(botStatLabel)
        
        view.addSubview(topScrollView)
        view.addSubview(botScrollView)
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            topStatLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            topStatLabel.leadingAnchor.constraint(equalTo: topScrollView.leadingAnchor),

            topScrollView.topAnchor.constraint(equalTo: topStatLabel.bottomAnchor, constant: 4.0),
            topScrollView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            topScrollView.widthAnchor.constraint(equalToConstant: 300.0),
            topScrollView.heightAnchor.constraint(equalToConstant: 200.0),
            
            botScrollView.topAnchor.constraint(equalTo: topScrollView.bottomAnchor, constant: 12.0),
            botScrollView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            botScrollView.widthAnchor.constraint(equalToConstant: 300.0),
            botScrollView.heightAnchor.constraint(equalToConstant: 200.0),
            
            botStatLabel.topAnchor.constraint(equalTo: botScrollView.bottomAnchor, constant: 4.0),
            botStatLabel.leadingAnchor.constraint(equalTo: botScrollView.leadingAnchor),
            
            topView.widthAnchor.constraint(equalToConstant: 300.0),
            topView.heightAnchor.constraint(equalToConstant: 200.0),
            
            botView.widthAnchor.constraint(equalToConstant: 300.0 * 10.0),
            botView.heightAnchor.constraint(equalToConstant: 200.0 * 10.0),
            
            topLabel.topAnchor.constraint(equalTo: topView.topAnchor, constant: 8.0),
            topLabel.leadingAnchor.constraint(equalTo: topView.leadingAnchor, constant: 8.0),

            botLabel.topAnchor.constraint(equalTo: botView.topAnchor, constant: 8.0 * 10.0),
            botLabel.leadingAnchor.constraint(equalTo: botView.leadingAnchor, constant: 8.0 * 10.0),
            
            topView.topAnchor.constraint(equalTo: topScrollView.contentLayoutGuide.topAnchor),
            topView.leadingAnchor.constraint(equalTo: topScrollView.contentLayoutGuide.leadingAnchor),
            topView.trailingAnchor.constraint(equalTo: topScrollView.contentLayoutGuide.trailingAnchor),
            topView.bottomAnchor.constraint(equalTo: topScrollView.contentLayoutGuide.bottomAnchor),

            botView.topAnchor.constraint(equalTo: botScrollView.contentLayoutGuide.topAnchor),
            botView.leadingAnchor.constraint(equalTo: botScrollView.contentLayoutGuide.leadingAnchor),
            botView.trailingAnchor.constraint(equalTo: botScrollView.contentLayoutGuide.trailingAnchor),
            botView.bottomAnchor.constraint(equalTo: botScrollView.contentLayoutGuide.bottomAnchor),
            
        ])
        
        topLabel.textColor = .red
        topLabel.backgroundColor = .yellow
        topLabel.font = UIFont.systemFont(ofSize: 30.0, weight: .regular)
        topLabel.text = "Sample Text"
        
        botLabel.textColor = .red
        botLabel.backgroundColor = .yellow
        botLabel.font = UIFont.systemFont(ofSize: 30.0 * 10.0, weight: .regular)
        botLabel.text = "Sample Text"
        
        topScrollView.delegate = self
        topScrollView.minimumZoomScale = 1
        topScrollView.maximumZoomScale = 10
        
        botScrollView.delegate = self
        botScrollView.minimumZoomScale = 0.1
        botScrollView.maximumZoomScale = 1
        
        topScrollView.zoomScale = topScrollView.minimumZoomScale
        botScrollView.zoomScale = botScrollView.minimumZoomScale

        view.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        topScrollView.backgroundColor = .white
        botScrollView.backgroundColor = .white

        topStatLabel.font = UIFont.systemFont(ofSize: 14, weight: .light)
        topStatLabel.numberOfLines = 0
        botStatLabel.font = UIFont.systemFont(ofSize: 14, weight: .light)
        botStatLabel.numberOfLines = 0
        
        let t = UITapGestureRecognizer(target: self, action: #selector(self.tapped(_:)))
        view.addGestureRecognizer(t)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        updateStatLabels()
    }
    
    func updateStatLabels() -> Void {
        var sTop = ""
        sTop += "Label Point Size: \(topLabel.font.pointSize)"
        sTop += "\n"
        sTop += "Label Frame: \(topLabel.frame)"
        sTop += "\n"
        sTop += "View Size: \(topView.bounds.size)"
        sTop += "\n"
        sTop += "Zoom Scale: \(String(format: "%0.1f", topScrollView.zoomScale))"
        
        var sBot = ""
        sBot += "Zoom Scale: \(String(format: "%0.1f", botScrollView.zoomScale))"
        sBot += "\n"
        sBot += "View Size: \(botView.bounds.size)"
        sBot += "\n"
        sBot += "Label Frame: \(botLabel.frame)"
        sBot += "\n"
        sBot += "Label Point Size: \(botLabel.font.pointSize)"
        
        topStatLabel.text = sTop
        botStatLabel.text = sBot
    }
    
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        if scrollView == topScrollView {
            return topView
        }
        return botView
    }
    
    @objc func tapped(_ g: UITapGestureRecognizer) -> Void {
        
        if Int(topScrollView.zoomScale) == Int(topScrollView.maximumZoomScale) {
            topScrollView.zoomScale = topScrollView.minimumZoomScale
        } else {
            topScrollView.zoomScale += 1
        }
        topScrollView.contentOffset = .zero

        // comparing floating point directly will fail, so round the values
        if round(botScrollView.zoomScale * 10) == round(botScrollView.maximumZoomScale * 10) {
            botScrollView.zoomScale = botScrollView.minimumZoomScale
        } else {
            botScrollView.zoomScale += 0.1
        }

        botScrollView.contentOffset = .zero

        updateStatLabels()
    }
    
}

顶部滚动视图有一个300 x 200带有 30 点标签的视图,允许从1 to 10.

底部滚动视图有一个3000 x 2000带有 300 点标签的视图,允许从0.1 to 1.0.

每次点击屏幕时,scrollViews 将 zoomScale 分别增加 1 和 0.1。

它在最小比例下看起来像这样:

在此处输入图像描述

在 5 和 0.5 级:

在此处输入图像描述

在 10 和 1.0 比例下:

在此处输入图像描述


推荐阅读