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

我正在尝试使用Core Graphics构build一个橡皮擦工具,而且我发现制作高性能橡皮的难度令人难以置信 – 这一切都归结为:

CGContextSetBlendMode(context, kCGBlendModeClear)

如果你谷歌周围如何与核心graphics“擦除”,几乎每个答案都回来这个片段。 问题是它只是(显然)在位图上下文中工作。 如果你正在尝试实现交互式擦除,我不明白kCGBlendModeClear如何帮助你的 – 就我所知,你或多或less被locking在擦除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 ,其中包含所有以前绘制的笔触) curImage到当前的CGContext ,并呈现当前绘制的行(path)。 这不是完美的,但嘿,它的作品,它是合理的性能。

但是,因为kCGBlendModeNormal显然不能kCGBlendModeNormal图上下文之外工作,所以我不得不:

  1. 创build一个位图上下文( UIGraphicsBeginImageContextWithOptions )。
  2. 绘制我的离线缓冲区( eraseImage ,当橡皮擦工具打开时,它实际上是从curImage派生出来的 – 所以为了参数,真的和curImage )。
  3. 呈现当前绘制到位图上下文的“擦除线”(path)(使用kCGBlendModeClear清除像素)。
  4. 将整个图像提取到离屏缓冲区( curImage = UIGraphicsGetImageFromCurrentImageContext();
  5. 然后最后将屏幕外缓冲区溢出到视图的CGContext

这是可怕的,性能明智的。 使用仪器的时间工具,这是显而易见的问题,这种方法是:

  • UIGraphicsBeginImageContextWithOptions是昂贵的
  • 画屏幕缓冲区两次是昂贵的
  • 将整个图像提取到屏幕外的缓冲区是昂贵的

自然,代码在真正的iPad上performance可怕。

我不确定在这里做什么。 我一直想弄清楚如何清除非位图上下文中的像素,但据我所知,依靠kCGBlendModeClear是一个死胡同。

任何想法或build议? 其他iOS绘图应用程序如何处理擦除?


附加信息

我一直在玩CGLayer方法,因为看起来CGContextSetBlendMode(context, kCGBlendModeClear)可以在CGLayer工作,这CGLayer我所做的一些Googlesearch。

不过,我并不希望这种方法能够实现。 在drawRect绘制图层(甚至使用setNeedsDisplayInRect )是非常setNeedsDisplayInRect 。 核心graphics窒息将呈现在CGContextDrawLayerAtPoint (根据仪器)图层中的每个path。 据我所知,使用位图上下文在性能方面肯定是可取的 – 唯一的问题,当然是上面的问题( kCGBlendModeClear在将位图上下文移动到drawRect的主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在擦除背景图像上绘制“擦除”path

由于擦除背景图像的像素数据和擦除path都在同一图层中,所以具有清除像素的效果。

二维graphics跟随绘画范例。 当你正在绘画的时候,很难去除已经放在canvas上的油漆,但是很容易在上面添加更多的油漆。 与位图上下文的混合模式给你一个方法来做一些艰难的事情(刮油漆canvas)几行代码。 这几行代码并不是一个简单的计算操作(这就是为什么它的执行速度很慢)。

在不需要执行离屏位图缓冲的情况下,清除像素的最简单方法是在图像上绘制视图的背景。

 -(void)drawRect:(CGRect)rect { if (drawingStroke) { CGColor lineCgColor = lineColor.CGColor; if (eraseModeOn) { //Use concrete background color to display erasing. You could use the backgroundColor property of the view, or define a color here lineCgColor = [[self backgroundColor] CGColor]; } [curImage drawAtPoint:CGPointZero]; CGContextRef context = UIGraphicsGetCurrentContext(); CGContextAddPath(context, currentPath); CGContextSetLineCap(context, kCGLineCapRound); CGContextSetLineWidth(context, lineWidth); CGContextSetBlendMode(context, kCGBlendModeNormal); CGContextSetStrokeColorWithColor(context, lineCgColor); CGContextStrokePath(context); } else { [curImage drawAtPoint:CGPointZero]; } } 

更困难的(但更正确的)方法是在后台串行队列上进行图像编辑以响应编辑事件。 当你得到一个新的动作的时候,你可以在后台对图像缓冲区进行位图渲染。 当caching的图像准备就绪时,您可以调用setNeedsDisplay来允许在下一个更新周期内重新绘制视图。 这是更正确的drawRect:应尽快显示您的视图的内容,而不是处理编辑操作。

 @interface ImageEditor : UIView @property (nonatomic, strong) UIImage * imageBuffer; @property (nonatomic, strong) dispatch_queue_t serialQueue; @end @implementation ImageEditor - (dispatch_queue_t) serialQueue { if (_serialQueue == nil) { _serialQueue = dispatch_queue_create("com.example.com.imagebuffer", DISPATCH_QUEUE_SERIAL); } return _serialQueue; } - (void)editingAction { dispatch_async(self.serialQueue, ^{ CGSize bufferSize = [self.imageBuffer size]; UIGraphicsBeginImageContext(bufferSize); CGContext context = UIGraphicsGetCurrentContext(); CGContextDrawImage(context, CGRectMake(0, 0, bufferSize.width, bufferSize.height), [self.imageBuffer CGImage]); //Do editing action, draw a clear line, solid line, etc self.imageBuffer = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); dispatch_async(dispatch_get_main_queue(), ^{ [self setNeedsDisplay]; }); }); } -(void)drawRect:(CGRect)rect { [self.imageBuffer drawAtPoint:CGPointZero]; } @end 

键是CGContextBeginTransparencyLayer并使用clearColor并设置CGContextSetBlendMode(context,kCGBlendModeClear);