我正在尝试使用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];
    }
}

画一条法线(!eraseModeOn)是可以接受的。我将屏幕外的绘图缓冲区(curImage,其中包含所有先前绘制的笔触)分配到当前的CGContext,并渲染当前正在绘制的线(路径)。它不是完美的,但是嘿,它有效,并且性能合理。

但是,因为kCGBlendModeNormal显然在位图上下文之外不起作用,所以我不得不:
  • 创建位图上下文(UIGraphicsBeginImageContextWithOptions)。
  • 绘制我的屏幕外缓冲区(eraseImage,实际上是在打开橡皮擦工具时从curImage派生的-就参数而言,它实际上与curImage几乎相同)。
  • 渲染当前绘制到位图上下文的“擦除线”(路径)(使用kCGBlendModeClear清除像素)。
  • 将整个图像提取到屏幕外缓冲区(curImage = UIGraphicsGetImageFromCurrentImageContext();)
  • 然后最后将屏幕外缓冲区变为 View 的CGContext

  • 从性能角度来看,这太可怕了。使用仪器的时间工具,很明显该方法的问题在哪里:
  • UIGraphicsBeginImageContextWithOptions很贵
  • 两次绘制屏幕外缓冲区非常昂贵
  • 将整个图像提取到屏幕外缓冲区很昂贵

  • 很自然,代码在真正的iPad上表现出惊人的性能。

    我不太确定该怎么做。我一直在尝试找出如何在非位图上下文中清除像素,但是据我所知,依靠kCGBlendModeClear是一个死胡同。

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

    附加信息

    我一直在使用CGLayer方法,因为根据我做过的一些谷歌搜索,CGContextSetBlendMode(context, kCGBlendModeClear)似乎可以在CGLayer中工作。

    但是,我不太希望这种方法能成功。用drawRect绘制图层(甚至使用setNeedsDisplayInRect)也非常不方便; Core Graphics陷入困境,将以CGContextDrawLayerAtPoint渲染层中的每个路径(根据Instruments)。据我所知,就性能而言,使用位图上下文绝对是首选-当然,唯一的问题就是上述问题(我将位图上下文分配到kCGBlendModeClear中的主要CGContext后,drawRect无法正常工作)。

    最佳答案

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

    - (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;
    }
    

    诀窍是将以下内容包装到CGContextBeginTransparencyLayer/CGContextEndTransparencyLayer调用中:
  • 将擦除背景图像添加到上下文中
  • 使用kCGBlendModeClear
  • 在擦除背景图像的顶部绘制“erase”路径

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

    10-08 12:23