CGContext:如何在位图上下文之外擦除像素(例如kCGBlendModeClear)?

Rob*_*Rob 11 core-graphics objective-c cgcontext ios

我正在尝试使用Core Graphics构建一个橡皮擦工具,我发现制作高性能橡皮擦非常困难 - 这一切都归结为:

CGContextSetBlendMode(context, kCGBlendModeClear)

如果你四处搜索如何使用Core Graphics"擦除",那么几乎每个答案都会带回来.问题是它(仅显然)在位图上下文中起作用.如果你正在尝试实现交互式擦除,我看不出kCGBlendModeClear你有多大帮助 - 据我所知,你或多或少地被锁定在屏幕上和屏幕外擦除UIImage/ CGImage并在着名的非图像中绘制图像高效的[UIView drawRect].

这是我能做的最好的:

-(void)drawRect:(CGRect)rect
{
    if (drawingStroke) {
        if (eraseModeOn) {
            UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
            CGContextRef context = UIGraphicsGetCurrentContext();
            [eraseImage drawAtPoint:CGPointZero];
            CGContextAddPath(context, currentPath);
            CGContextSetLineCap(context, kCGLineCapRound);
            CGContextSetLineWidth(context, lineWidth);
            CGContextSetBlendMode(context, kCGBlendModeClear);
            CGContextSetLineWidth(context, ERASE_WIDTH);
            CGContextStrokePath(context);
            curImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            [curImage drawAtPoint:CGPointZero];
        } else {
            [curImage drawAtPoint:CGPointZero];
            CGContextRef context = UIGraphicsGetCurrentContext();
            CGContextAddPath(context, currentPath);
            CGContextSetLineCap(context, kCGLineCapRound);
            CGContextSetLineWidth(context, lineWidth);
            CGContextSetBlendMode(context, kCGBlendModeNormal);
            CGContextSetStrokeColorWithColor(context, lineColor.CGColor);
            CGContextStrokePath(context);
        }
    } else {
        [curImage drawAtPoint:CGPointZero];
    }
}
Run Code Online (Sandbox Code Playgroud)

绘制法线(!eraseModeOn)是可以接受的; 我正在将我的屏幕外绘图缓冲区(curImage包含所有先前绘制的笔划)与当前图像进行对比CGContext,并且我正在渲染当前绘制的线条(路径).它并不完美,但嘿,它有效,并且它的性能相当合理.

但是,因为kCGBlendModeNormal显然在位图上下文之外不起作用,我不得不:

  1. 创建位图上下文(UIGraphicsBeginImageContextWithOptions).
  2. 绘制我的屏幕外缓冲区(eraseImage实际上是从curImage打开橡皮擦工具时得到的 - 所以与curImage参数真的非常相似).
  3. 渲染当前正在绘制到位图上下文的"擦除线"(路径)(kCGBlendModeClear用于清除像素).
  4. 将整个图像提取到屏幕外缓冲区(curImage = UIGraphicsGetImageFromCurrentImageContext();)
  5. 然后最终将屏幕外缓冲区blit到视图中 CGContext

这很糟糕,表现明智.使用Instrument的Time工具,很明显这个方法的问题在于:

  • UIGraphicsBeginImageContextWithOptions 太贵了
  • 两次绘制屏幕外缓冲区是很昂贵的
  • 将整个图像提取到屏幕外缓冲区是很昂贵的

很自然地,代码在真正的iPad上表现得非常糟糕.

我不确定该怎么做.我一直试图弄清楚如何清除非位图上下文中的像素,但据我所知,依赖kCGBlendModeClear是一个死胡同.

有什么想法或建议吗?其他iOS绘图应用程序如何处理擦除?


附加信息

我一直在玩一种CGLayer方法,因为看起来它CGContextSetBlendMode(context, kCGBlendModeClear)CGLayer基于我已经完成的一些谷歌搜索工作.

但是,我并不希望这种方法能够成功.在drawRect(甚至使用setNeedsDisplayInRect)中绘制图层是非常不具有效果的; 令人窒息的核心图形将渲染图层中的每个路径CGContextDrawLayerAtPoint(根据Instruments).据我所知,使用位图背景是在性能方面绝对是最好在这里-唯一的问题,当然,作为上述问题(kCGBlendModeClear没有工作后,我的blit位图上下文主要CGContextdrawRect).

Rob*_*Rob 8

我通过使用以下代码设法获得了良好的结果:

- (void)drawRect:(CGRect)rect
{
    if (drawingStroke) {
        if (eraseModeOn) {
            CGContextRef context = UIGraphicsGetCurrentContext();
            CGContextBeginTransparencyLayer(context, NULL);
            [eraseImage drawAtPoint:CGPointZero];

            CGContextAddPath(context, currentPath);
            CGContextSetLineCap(context, kCGLineCapRound);
            CGContextSetLineWidth(context, ERASE_WIDTH);
            CGContextSetBlendMode(context, kCGBlendModeClear);
            CGContextSetStrokeColorWithColor(context, [[UIColor clearColor] CGColor]);
            CGContextStrokePath(context);
            CGContextEndTransparencyLayer(context);
        } else {
            [curImage drawAtPoint:CGPointZero];
            CGContextRef context = UIGraphicsGetCurrentContext();
            CGContextAddPath(context, currentPath);
            CGContextSetLineCap(context, kCGLineCapRound);
            CGContextSetLineWidth(context, self.lineWidth);
            CGContextSetBlendMode(context, kCGBlendModeNormal);
            CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
            CGContextStrokePath(context);
        }
    } else {
        [curImage drawAtPoint:CGPointZero];
    }

    self.empty = NO;
}
Run Code Online (Sandbox Code Playgroud)

诀窍是将以下内容包含在CGContextBeginTransparencyLayer/ CGContextEndTransparencyLayercalls中:

  • 将擦除背景图像模糊到上下文
  • 使用,在擦除背景图像上绘制"擦除"路径 kCGBlendModeClear

由于擦除背景图像的像素数据和擦除路径都在同一层中,因此它具有清除像素的效果.