【问题标题】:How to render view into image faster?如何更快地将视图渲染为图像?
【发布时间】:2013-10-04 16:37:45
【问题描述】:

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

    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 吗?

【问题讨论】:

标签: ios ios6 uiview core-graphics calayer


【解决方案1】:

没有。在 iOS6 中,renderInContext: 是唯一的方法。它很慢。它使用 CPU。

呈现 UIKit 内容的方式

renderInContext:

[view.layer renderInContext:UIGraphicsGetCurrentContext()];
  • 需要 iOS 2.0。 It runs in the 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。


如果有任何帮助,这里有一些代码可以在捕获尝试仍在运行时丢弃。

这会一次将块执行限制为一个,并丢弃其他块。来自this SO answer

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,有什么办法吗?
  • 没有。如果有任何帮助,请尝试对您要渲染的图层进行光栅化,并使用带有信号量的串行队列,以便一次执行一个 renderInContext:。我在上面发布了一些代码。但同样,renderInContext: 是捕获 UIKit 内容的唯一方法,而且速度很慢。
  • 只是注意到最初的问题是关于渲染到图像中,而这个答案显示渲染到视图中。
  • renderInContext 和 drawViewHierarchyInRect:afterScreenUpdates:绘制到可以使用 UIGraphicsGetImageFromCurrentImageContext() 转换为 UIImage 的上下文中。您还可以使用 drawViewHierarchyInRect 将视图绘制到上下文中。有时间我会尝试添加一些示例代码。
  • 请注意,如果您打算从生成的 UIView 中获取图像,snapshotViewAfterScreenUpdates: 将不起作用:stackoverflow.com/questions/20203682/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多