从 CALayers 获取位图,renderInContext 的任何替代方案

发布于 2024-12-01 12:00:00 字数 367 浏览 1 评论 0原文

我有许多 CALayer,它们是在我的应用程序运行时动态创建的,并且我需要能够生成这些的单个位图,稍后将对其进行屏蔽。

当我需要创建蒙版时,CALayers 已经绘制到背景(也使用 shouldRasterize = YES),并且使用 renderInContext 我能够获取位图。然而,随着 CAlayer 数量的增加,renderInContext 引起的停顿变得越来越长。是否有其他方法可以用来 renderInContext,或者有其他方法可以使用它来阻止我的应用程序暂时冻结?

理想的情况是直接从内存/缓冲区/缓存访问已经绘制的像素数据,而不使用 OpenGL,但我不确定这是否可以通过 CoreAnimation 实现。

谢谢,任何额外的信息都会非常有用!

I have many CALayers which are created on the fly while my app is running, and I need to be able to produce a single bitmap of these, which will later be masked.

When I need to create the mask, the CALayers are already drawn to the background (also using shouldRasterize = YES) , and using renderInContext I am able to get a bitmap. However, as the amount of CAlayers increases, the pause caused by renderInContext gets longer and longer. Is there an alternative I can use to renderInContext, or an alternative way I can use it to stop my app temporarily freezing?

The ideal would be to access the already drawn pixel data directly from memory/buffer/cache without using OpenGL, but I am unsure if this is possible with CoreAnimation.

Thanks, any additional information at all would be very useful!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

握住我的手 2024-12-08 12:00:00

Rob 认为 renderInContext: 是此处使用的正确方法,这是正确的。在上下文中渲染实际上是将图层的像素数据渲染到上下文中。这是一个示例应用程序,它将在后台线程上绘制 10,000 个图层...

该应用程序执行以下操作:

  1. 创建一个 UIView
  2. 将 10,000 个图层添加到该视图的图层
  3. 当您触摸屏幕时开始渲染(即它是一个 iOS 示例应用程序)
  4. 创建背景thread
  5. 将 UIView 的图层渲染到上下文中(该上下文又渲染其子图层)
  6. 使用渲染上下文的内容创建 UIImage
  7. 将新图像添加到屏幕在 UIImageView 中,
  8. 它在主线程上运行计时器时完成了所有这一切,表明后台线程实际上不会阻塞主线程

这是代码...

首先,创建一个带有很多层:

@implementation C4WorkSpace {
    UIView *v;
    dispatch_queue_t backgroundRenderQueue;
    CFTimeInterval beginTime;
    NSTimer *timer;
    NSInteger timerCallCount;
}

-(void)setup {
    //create a view to hold a bunch of CALayers
    v = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)];
    v.center = CGPointMake(384,512);

    //create a buch of CALayers and add them to a view
    for(int i = 0; i < 10000; i++) {
        CALayer *l = [[CALayer alloc] init];
        l.frame = CGRectMake([self random:390],[self random:390],10,10);
        l.backgroundColor = [UIColor blueColor].CGColor;
        l.borderColor = [UIColor orangeColor].CGColor;
        l.borderWidth = 2.0f;
        [v.layer addSublayer:l];
    }
    //add the view to the application's main view
    [self.view addSubview:v];
}

-(NSInteger)random:(NSInteger)value {
    srandomdev();
    return ((NSInteger)random())%value;
}

第二,创建一个方法来启动计时器,然后触发渲染...

-(void)touchesBegan {
    timer = [NSTimer scheduledTimerWithTimeInterval:0.03f target:self selector:@selector(printTime) userInfo:nil repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    [self render];
}

-(void)printTime {
    NSLog(@"%d (main thread running)",++timerCallCount);
}

第三,创建一个带有回调方法的渲染循环,该回调方法在渲染完成后将图像显示在屏幕上。

-(void)render {
    NSLog(@"render was called");
    //create the queue
    backgroundRenderQueue = dispatch_queue_create("backgroundRenderQueue",DISPATCH_QUEUE_CONCURRENT);    
    //create a async call on the background queue
    dispatch_async(backgroundRenderQueue, ^{
        //create a cgcontext
        NSUInteger width = (NSUInteger)v.frame.size.width;
        NSUInteger height = (NSUInteger)v.frame.size.height;
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        NSUInteger bytesPerPixel = 4;
        NSUInteger bytesPerRow = bytesPerPixel * width;
        unsigned char *rawData = malloc(height * bytesPerRow);
        NSUInteger bitsPerComponent = 8;
        CGContextRef context = CGBitmapContextCreate(rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

        //render the layer and its subviews
        [v.layer renderInContext:context];

        //create a callback async on the main queue when rendering is complete
        dispatch_async(dispatch_get_main_queue(), ^{
            //create an image from the context
            UIImage *m = [UIImage imageWithCGImage:CGBitmapContextCreateImage(context)];
            UIImageView *uiiv = [[UIImageView alloc] initWithImage:m];
            //add the image view to the main view
            [self.view addSubview:uiiv];
            CGColorSpaceRelease(colorSpace);
            CGContextRelease(context);
            NSLog(@"rendering complete");
            [timer invalidate];
        });
    });
}

注意:如果您的图层并非全部位于同一个子图层中,您可以轻松调用 for 循环,将上下文转换为每个 CALayer 的原点或从每个 CALayer 的原点进行转换,并将每个图层单独绘制到上下文本身中

。我运行它,得到以下输出:

2013-03-18 07:14:28.617 C4iOS[21086:907] render was called
2013-03-18 07:14:28.648 C4iOS[21086:907] 1 (main thread running)
2013-03-18 07:14:28.680 C4iOS[21086:907] 2 (main thread running)
2013-03-18 07:14:28.709 C4iOS[21086:907] 3 (main thread running)
2013-03-18 07:14:28.737 C4iOS[21086:907] 4 (main thread running)
2013-03-18 07:14:28.767 C4iOS[21086:907] 5 (main thread running)
2013-03-18 07:14:28.798 C4iOS[21086:907] 6 (main thread running)
2013-03-18 07:14:28.828 C4iOS[21086:907] 7 (main thread running)
2013-03-18 07:14:28.859 C4iOS[21086:907] 8 (main thread running)
2013-03-18 07:14:28.887 C4iOS[21086:907] 9 (main thread running)
2013-03-18 07:14:28.917 C4iOS[21086:907] 10 (main thread running)
2013-03-18 07:14:28.948 C4iOS[21086:907] 11 (main thread running)
2013-03-18 07:14:28.978 C4iOS[21086:907] 12 (main thread running)
2013-03-18 07:14:29.010 C4iOS[21086:907] 13 (main thread running)
2013-03-18 07:14:29.037 C4iOS[21086:907] 14 (main thread running)
2013-03-18 07:14:29.069 C4iOS[21086:907] 15 (main thread running)
2013-03-18 07:14:29.097 C4iOS[21086:907] 16 (main thread running)
2013-03-18 07:14:29.130 C4iOS[21086:907] 17 (main thread running)
2013-03-18 07:14:29.159 C4iOS[21086:907] 18 (main thread running)
2013-03-18 07:14:29.189 C4iOS[21086:907] 19 (main thread running)
2013-03-18 07:14:29.217 C4iOS[21086:907] 20 (main thread running)
2013-03-18 07:14:29.248 C4iOS[21086:907] 21 (main thread running)
2013-03-18 07:14:29.280 C4iOS[21086:907] 22 (main thread running)
2013-03-18 07:14:29.309 C4iOS[21086:907] 23 (main thread running)
2013-03-18 07:14:29.337 C4iOS[21086:907] 24 (main thread running)
2013-03-18 07:14:29.369 C4iOS[21086:907] 25 (main thread running)
2013-03-18 07:14:29.397 C4iOS[21086:907] 26 (main thread running)
2013-03-18 07:14:29.405 C4iOS[21086:907] rendering complete

Rob is right about renderInContext: being the right method to use here. Render in context does actually render the layer's pixel data into a context. Here's a sample application that will draw 10,000 layers on a background thread...

The application does the following:

  1. Create's a UIView
  2. Adds 10,000 layers to that view's layer
  3. Rendering begins when you touch the screen (i.e. it's an iOS sample app)
  4. Creates a background thread
  5. Renders the UIView's layer into a context (which in turn renders its sublayers)
  6. Creates a UIImage with the content of the render context
  7. Adds the new image to the screen in a UIImageView
  8. It does all this while running a timer on the main thread, showing that the background thread actually doesn't block the main thread

Here is the code...

First, create a subview with a lot of layers:

@implementation C4WorkSpace {
    UIView *v;
    dispatch_queue_t backgroundRenderQueue;
    CFTimeInterval beginTime;
    NSTimer *timer;
    NSInteger timerCallCount;
}

-(void)setup {
    //create a view to hold a bunch of CALayers
    v = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)];
    v.center = CGPointMake(384,512);

    //create a buch of CALayers and add them to a view
    for(int i = 0; i < 10000; i++) {
        CALayer *l = [[CALayer alloc] init];
        l.frame = CGRectMake([self random:390],[self random:390],10,10);
        l.backgroundColor = [UIColor blueColor].CGColor;
        l.borderColor = [UIColor orangeColor].CGColor;
        l.borderWidth = 2.0f;
        [v.layer addSublayer:l];
    }
    //add the view to the application's main view
    [self.view addSubview:v];
}

-(NSInteger)random:(NSInteger)value {
    srandomdev();
    return ((NSInteger)random())%value;
}

Second, create a method that will start a timer and then triggers the render...

-(void)touchesBegan {
    timer = [NSTimer scheduledTimerWithTimeInterval:0.03f target:self selector:@selector(printTime) userInfo:nil repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    [self render];
}

-(void)printTime {
    NSLog(@"%d (main thread running)",++timerCallCount);
}

Third, create a render loop with a callback method that puts an image on the screen after rendering is completed.

-(void)render {
    NSLog(@"render was called");
    //create the queue
    backgroundRenderQueue = dispatch_queue_create("backgroundRenderQueue",DISPATCH_QUEUE_CONCURRENT);    
    //create a async call on the background queue
    dispatch_async(backgroundRenderQueue, ^{
        //create a cgcontext
        NSUInteger width = (NSUInteger)v.frame.size.width;
        NSUInteger height = (NSUInteger)v.frame.size.height;
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        NSUInteger bytesPerPixel = 4;
        NSUInteger bytesPerRow = bytesPerPixel * width;
        unsigned char *rawData = malloc(height * bytesPerRow);
        NSUInteger bitsPerComponent = 8;
        CGContextRef context = CGBitmapContextCreate(rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

        //render the layer and its subviews
        [v.layer renderInContext:context];

        //create a callback async on the main queue when rendering is complete
        dispatch_async(dispatch_get_main_queue(), ^{
            //create an image from the context
            UIImage *m = [UIImage imageWithCGImage:CGBitmapContextCreateImage(context)];
            UIImageView *uiiv = [[UIImageView alloc] initWithImage:m];
            //add the image view to the main view
            [self.view addSubview:uiiv];
            CGColorSpaceRelease(colorSpace);
            CGContextRelease(context);
            NSLog(@"rendering complete");
            [timer invalidate];
        });
    });
}

NOTE: if your layers aren't all in the same sublayer, you can easily call a for loop that translates the context to and from the origin of each CALayer and draw each layer individually into the context itself

When I run this I get the following output:

2013-03-18 07:14:28.617 C4iOS[21086:907] render was called
2013-03-18 07:14:28.648 C4iOS[21086:907] 1 (main thread running)
2013-03-18 07:14:28.680 C4iOS[21086:907] 2 (main thread running)
2013-03-18 07:14:28.709 C4iOS[21086:907] 3 (main thread running)
2013-03-18 07:14:28.737 C4iOS[21086:907] 4 (main thread running)
2013-03-18 07:14:28.767 C4iOS[21086:907] 5 (main thread running)
2013-03-18 07:14:28.798 C4iOS[21086:907] 6 (main thread running)
2013-03-18 07:14:28.828 C4iOS[21086:907] 7 (main thread running)
2013-03-18 07:14:28.859 C4iOS[21086:907] 8 (main thread running)
2013-03-18 07:14:28.887 C4iOS[21086:907] 9 (main thread running)
2013-03-18 07:14:28.917 C4iOS[21086:907] 10 (main thread running)
2013-03-18 07:14:28.948 C4iOS[21086:907] 11 (main thread running)
2013-03-18 07:14:28.978 C4iOS[21086:907] 12 (main thread running)
2013-03-18 07:14:29.010 C4iOS[21086:907] 13 (main thread running)
2013-03-18 07:14:29.037 C4iOS[21086:907] 14 (main thread running)
2013-03-18 07:14:29.069 C4iOS[21086:907] 15 (main thread running)
2013-03-18 07:14:29.097 C4iOS[21086:907] 16 (main thread running)
2013-03-18 07:14:29.130 C4iOS[21086:907] 17 (main thread running)
2013-03-18 07:14:29.159 C4iOS[21086:907] 18 (main thread running)
2013-03-18 07:14:29.189 C4iOS[21086:907] 19 (main thread running)
2013-03-18 07:14:29.217 C4iOS[21086:907] 20 (main thread running)
2013-03-18 07:14:29.248 C4iOS[21086:907] 21 (main thread running)
2013-03-18 07:14:29.280 C4iOS[21086:907] 22 (main thread running)
2013-03-18 07:14:29.309 C4iOS[21086:907] 23 (main thread running)
2013-03-18 07:14:29.337 C4iOS[21086:907] 24 (main thread running)
2013-03-18 07:14:29.369 C4iOS[21086:907] 25 (main thread running)
2013-03-18 07:14:29.397 C4iOS[21086:907] 26 (main thread running)
2013-03-18 07:14:29.405 C4iOS[21086:907] rendering complete
白云悠悠 2024-12-08 12:00:00

renderInContext: 是这里最好的工具,但您不需要在主线程上运行它。只需将其移至后台线程,它就会停止冻结您的应用程序。

renderInContext: is the best tool here, but you don't need to run it on the main thread. Just move this to a background thread and it'll stop freezing your app.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文