本文介绍了CGContext:如何在位图上下文之外擦除像素(例如kCGBlendModeClear)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to build an eraser tool using Core Graphics, and I'm finding it incredibly difficult to make a performant eraser - it all comes down to:

CGContextSetBlendMode(context,kCGBlendModeClear)

如果您想知道如何使用Core Graphics擦除,几乎每个答案都带有该片段。问题是它(仅显然)在位图上下文中起作用。如果您正在尝试实现交互式擦除,我不会看到 kCGBlendModeClear 如何帮助您 - 据我所知,您或多或少地被锁定在擦除上和离屏 UIImage / CGImage 并在着名的非高性能中绘制该图像[ UIView drawRect]

If you google around for how to "erase" with Core Graphics, almost every answer comes back with that snippet. The problem is it only (apparently) works in a bitmap context. If you're trying to implement interactive erasing, I don't see how kCGBlendModeClear helps you - as far as I can tell, you're more or less locked into erasing on and off-screen UIImage/CGImage and drawing that image in the famously non-performant [UIView drawRect].

这是我能做的最好的事情:

Here's the best I've been able to do:

-(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 ,其中包含所有先前绘制的笔划)blits到当前的 CGContext ,我正在渲染当前绘制的线(路径)。它并不完美,但嘿,它有效,并且它的性能相当合理。

Drawing a normal line (!eraseModeOn) is acceptably performant; I'm blitting my off-screen drawing buffer (curImage, which contains all previously drawn strokes) to the current CGContext, and I'm rendering the line (path) being currently drawn. It's not perfect, but hey, it works, and it's reasonably performant.

然而,因为 kCGBlendModeNormal 显然不是在位图上下文之外工作,我被迫:

However, because kCGBlendModeNormal apparently does not work outside of a bitmap context, I'm forced to:


  1. 创建一个位图上下文( UIGraphicsBeginImageContextWithOptions )。

  2. 绘制我的屏幕外缓冲区( eraseImage ,实际上是从 curImage 当橡皮擦工具打开时 - 为了论证而与 curImage 完全相同)。

  3. 渲染当前正在绘制到位图上下文的擦除行(路径)(使用 kCGBlendModeClear 来清除像素)。

  4. 提取整个图像进入屏幕外缓冲区( curImage = UIGraphicsGetImageFromCurrentImageContext();

  5. 然后最后将屏幕外缓冲区blit到视图的 CGContext

  1. Create a bitmap context (UIGraphicsBeginImageContextWithOptions).
  2. Draw my offscreen buffer (eraseImage, which is actually derived from curImage when the eraser tool is turned on - so really pretty much the same as curImage for arguments sake).
  3. Render the "erase line" (path) currently being drawn to the bitmap context (using kCGBlendModeClear to clear pixels).
  4. Extract the entire image into the offscreen buffer (curImage = UIGraphicsGetImageFromCurrentImageContext();)
  5. And then finally blit the offscreen buffer to the view's CGContext

这太糟糕了,性能明智。使用Instrument的Time工具,很明显这个方法的问题在哪里:

That's horrible, performance-wise. Using Instrument's Time tool, it's painfully obvious where the problems with this method are:


  • UIGraphicsBeginImageContextWithOptions 很贵

  • 将屏幕外缓冲区绘制两次是昂贵的

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

  • UIGraphicsBeginImageContextWithOptions is expensive
  • Drawing the offscreen buffer twice is expensive
  • Extracting the entire image into an offscreen buffer is expensive

当然,代码在真正的iPad上表现得非常糟糕。

So naturally, the code performs horribly on a real iPad.

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

I'm not really sure what to do here. I've been trying to figure out how to clear pixels in a non-bitmap context, but as far as I can tell, relying on kCGBlendModeClear is a dead-end.

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

Any thoughts or suggestions? How do other iOS drawing apps handle erase?

其他信息

我一直在使用 CGLayer 方法,因为它确实出现 CGContextSetBlendMode(context,kCGBlendModeClear) 将基于我已经完成的一些谷歌搜索在 CGLayer 中工作。

I've been playing around with a CGLayer approach, as it does appear that CGContextSetBlendMode(context, kCGBlendModeClear) will work in a CGLayer based on a bit of googling I've done.

但是,我并不希望这种方法能够成功。在 drawRect 中绘制图层(即使使用 setNeedsDisplayInRect )也非常缺乏性能;令人窒息的核心图形将在 CGContextDrawLayerAtPoint 中呈现图层中的每个路径(根据Instruments)。据我所知,在性能方面,使用位图上下文绝对是可取的 - 唯一的问题,当然是上面的问题( kCGBlendModeClear 在我之后无法工作将位图上下文blit到 CGRtext 中的 drawRect )。

However, I'm not super hopeful that this approach will pan out. Drawing the layer in drawRect (even using setNeedsDisplayInRect) is hugely non-performant; Core Graphics is choking up will rendering each path in the layer in CGContextDrawLayerAtPoint (according to Instruments). As far as I can tell, using a bitmap context is definitely preferable here in terms of performance - the only problem, of course, being the above question (kCGBlendModeClear not working after I blit the bitmap context to the main CGContext in drawRect).

推荐答案

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

I've managed to get good results by using the following code:

- (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 来电:

The trick was to wrap the following into CGContextBeginTransparencyLayer / CGContextEndTransparencyLayer calls:


  • Blitting擦除背景图片上下文

  • 使用 kCGBlendModeClear
  • $ b $在擦除背景图像上绘制擦除路径b
  • Blitting the erase background image to the context
  • Drawing the "erase" path on top of the erase background image, using kCGBlendModeClear

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

Since both the erase background image's pixel data and the erase path are in the same layer, it has the effect of clearing the pixels.

这篇关于CGContext:如何在位图上下文之外擦除像素(例如kCGBlendModeClear)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-05 14:13