如何更快地将视图渲染成图像?

24

我正在制作一个放大镜应用程序,允许用户触摸屏幕并移动手指,将会有一个随着手指路径移动的放大镜。我是通过截屏并将图像分配给放大镜图像视图来实现的,如下所示:

    CGSize imageSize = frame.size;
    UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0);
    CGContextRef c = UIGraphicsGetCurrentContext();
    CGContextScaleCTM(c, scaleFactor, scaleFactor);
    CGContextConcatCTM(c, CGAffineTransformMakeTranslation(-frame.origin.x, -frame.origin.y));
    [self.layer renderInContext:c];
    UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return screenshot;

问题在于self.layer renderInContext很慢,所以当用户移动手指时会感到不流畅。我尝试在其他线程中运行self.layer renderInContext,但是这使放大镜中的图像看起来很奇怪,因为放大镜中的图像出现了延迟。
有没有更好的方法将视图渲染成图像?renderInContext:使用GPU吗?

根据 Polishing Your User Interface Rotations 的说法,回答你的问题,renderInContext 是在 CPU 上执行的。 - Rob
1个回答

85

iOS6中,使用renderInContext:是唯一的方法。它速度较慢且使用CPU。

渲染UIKit内容的方法

renderInContext:

[view.layer renderInContext:UIGraphicsGetCurrentContext()];
  • 需要 iOS 2.0 版本以上支持。它在 CPU 上运行
  • 它不会捕捉带有非仿射变换、OpenGL 或视频内容的视图。
  • 如果有动画正在运行,你可以选择捕捉:
    • view.layer,捕捉动画的最终帧。
    • view.presentationLayer,捕捉动画当前帧。

snapshotViewAfterScreenUpdates:

UIView *snapshot = [view snapshotViewAfterScreenUpdates:YES];
  • 需要iOS 7或更高版本。
  • 这是最快的方法。
  • contents视图是不可变的。如果您想应用效果,则不太好。
  • 它可以捕获所有内容类型(UIKit、OpenGL或视频)。

resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets

[view resizableSnapshotViewFromRect:rect afterScreenUpdates:YES withCapInsets:edgeInsets]
  • iOS 7及以上版本可用。
  • snapshotViewAfterScreenUpdates:相同,但具有可调整大小的插图区。 content也是不可变的。

drawViewHierarchyInRect:afterScreenUpdates:

[view drawViewHierarchyInRect:rect afterScreenUpdates:YES];
  • 需要 iOS 7 及以上版本。
  • 它在当前上下文中绘制。
  • 根据第 226 会话,它比 renderInContext: 更快。

请参阅 WWDC 2013 会议第 226 Implementing Engaging UI on iOS 有关新的快照 API 的内容。


如果有帮助的话,这里是一些代码可以在一个截图尝试仍在运行时丢弃其他尝试。

这将块执行节流到一次,并且丢弃其他。来自此 SO 答案

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t renderQueue = dispatch_queue_create("com.throttling.queue", NULL);

- (void) capture {
    if (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW) == 0) {
        dispatch_async(renderQueue, ^{
            // capture
            dispatch_semaphore_signal(semaphore);
        });
    }
}

这是在做什么?

  • 创建一个信号量用于一个(1)资源。
  • 创建一个串行队列。
  • DISPATCH_TIME_NOW 表示超时不存在,因此它在红灯亮起时立即返回非零值。因此,不执行 if 语句中的内容。
  • 如果绿灯亮起,异步运行块并再次设置绿灯。

有没有其他方法可以替代renderInContext? - NOrder
1
不行。如果有帮助的话,尝试对要渲染的图层进行光栅化,并使用一个信号量的串行队列,以便一次只做一个 renderInContext: 操作。我会在上面发布一些代码。但是,请注意,renderInContext: 是捕获 UIKit 内容的唯一方法,它速度较慢。 - Jano
1
只是注意到原始问题询问的是将其渲染为图像,而这个答案展示了将其渲染为视图。 - Victor Engel
1
renderInContext和drawViewHierarchyInRect:afterScreenUpdates:可以绘制到上下文中,然后使用UIGraphicsGetImageFromCurrentImageContext()将其转换为UIImage。您还可以使用drawViewHierarchyInRect将视图绘制到上下文中。我会尽快添加一些示例代码。 - Jano
1
请注意,如果您计划从生成的UIView中获取图像,则snapshotViewAfterScreenUpdates:将无法工作:https://dev59.com/rWIj5IYBdhLWcg3wflIy。 - felippeduran
显示剩余3条评论

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