问题描述
我正在尝试使用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:
- 创建一个位图上下文(
UIGraphicsBeginImageContextWithOptions
)。 - 绘制我的屏幕外缓冲区(
eraseImage
,实际上是从curImage 当橡皮擦工具打开时 - 为了论证而与
curImage
完全相同)。 - 渲染当前正在绘制到位图上下文的擦除行(路径)(使用
kCGBlendModeClear
来清除像素)。 - 提取整个图像进入屏幕外缓冲区(
curImage = UIGraphicsGetImageFromCurrentImageContext();
) - 然后最后将屏幕外缓冲区blit到视图的
CGContext
- Create a bitmap context (
UIGraphicsBeginImageContextWithOptions
). - Draw my offscreen buffer (
eraseImage
, which is actually derived fromcurImage
when the eraser tool is turned on - so really pretty much the same ascurImage
for arguments sake). - Render the "erase line" (path) currently being drawn to the bitmap context (using
kCGBlendModeClear
to clear pixels). - Extract the entire image into the offscreen buffer (
curImage = UIGraphicsGetImageFromCurrentImageContext();
) - 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)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!