[英]CGContext: how do I erase pixels (e.g. kCGBlendModeClear) outside of a bitmap context?
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: 我正在尝试使用Core Graphics构建一个橡皮擦工具,我发现制作高性能橡皮擦非常困难 - 这一切都归结为:
CGContextSetBlendMode(context, kCGBlendModeClear)
If you google around for how to "erase" with Core Graphics, almost every answer comes back with that snippet. 如果你四处搜索如何使用Core Graphics“擦除”,那么几乎每个答案都会带回来。 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]
. 如果您正在尝试实现交互式擦除,我不会看到
kCGBlendModeClear
如何帮助您 - 据我所知,您或多或少地锁定在屏幕上和屏幕外的UIImage
/ CGImage
并在着名的非执行[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];
}
}
Drawing a normal line ( !eraseModeOn
) is acceptably performant; 绘制法线(
!eraseModeOn
)是可以接受的; 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. 我正在将我的屏幕外绘图缓冲区(
curImage
,其中包含所有先前绘制的笔划)与当前的CGContext
,并且我正在渲染当前绘制的线(路径)。 It's not perfect, but hey, it works, and it's reasonably performant. 它并不完美,但嘿,它有效,并且它的性能相当合理。
However, because kCGBlendModeNormal
apparently does not work outside of a bitmap context, I'm forced to: 但是,因为
kCGBlendModeNormal
显然不能kCGBlendModeNormal
图上下文之外工作,所以我不得不:
UIGraphicsBeginImageContextWithOptions
). UIGraphicsBeginImageContextWithOptions
)。 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). eraseImage
,实际上是在打开橡皮擦工具时从curImage
派生的 - 所以与参数的curImage
实际上非常相似)。 kCGBlendModeClear
to clear pixels). kCGBlendModeClear
清除像素)。 curImage = UIGraphicsGetImageFromCurrentImageContext();
) curImage = UIGraphicsGetImageFromCurrentImageContext();
) CGContext
CGContext
That's horrible, performance-wise. 这很糟糕,表现明智。 Using Instrument's Time tool, it's painfully obvious where the problems with this method are:
使用Instrument的Time工具,很明显这个方法的问题在于:
UIGraphicsBeginImageContextWithOptions
is expensive UIGraphicsBeginImageContextWithOptions
很昂贵 So naturally, the code performs horribly on a real iPad. 很自然地,代码在真正的iPad上表现得非常糟糕。
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. 我一直试图弄清楚如何在非位图上下文中清除像素,但据我所知,依赖于
kCGBlendModeClear
是一个死胡同。
Any thoughts or suggestions? 有什么想法或建议吗? How do other iOS drawing apps handle erase?
其他iOS绘图应用程序如何处理擦除?
Additional Info 附加信息
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. 我一直在使用
CGLayer
方法,因为看起来CGContextSetBlendMode(context, kCGBlendModeClear)
将基于我已经完成的一些谷歌搜索在CGLayer
工作。
However, I'm not super hopeful that this approach will pan out. 但是,我并不希望这种方法能够成功。 Drawing the layer in
drawRect
(even using setNeedsDisplayInRect
) is hugely non-performant; 在
drawRect
绘制图层(甚至使用setNeedsDisplayInRect
)是非常不具有效果的; Core Graphics is choking up will rendering each path in the layer in CGContextDrawLayerAtPoint
(according to Instruments). 令人窒息的核心图形将渲染
CGContextDrawLayerAtPoint
中的每个路径(根据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
). 据我所知,在性能方面,使用位图上下文绝对是可取的 - 当然,唯一的问题是上述问题(
kCGBlendModeClear
在我将位图上下文blit到drawRect
的主CGContext
后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;
}
The trick was to wrap the following into CGContextBeginTransparencyLayer
/ CGContextEndTransparencyLayer
calls: 诀窍是将以下内容包装到
CGContextBeginTransparencyLayer
/ CGContextEndTransparencyLayer
调用中:
kCGBlendModeClear
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. 由于擦除背景图像的像素数据和擦除路径都在同一层中,因此它具有清除像素的效果。
2D graphics following painting paradigms. 绘画范式之后的2D图形。 When you are painting, it's hard to remove paint you've already put on the canvas, but super easy to add more paint on top.
当你画画时,很难去除你已经放在画布上的油漆,但是很容易在上面添加更多的油漆。 The blend modes with a bitmap context give you a way to do something hard (scrape paint off the canvas) with few lines of code.
具有位图上下文的混合模式为您提供了一种方法,可以用很少的代码行来做一些难的事情(刮掉画布上的油漆)。 The few lines of code do not make it an easy computing operation (which is why it performs slowly).
几行代码不能使它成为一个简单的计算操作(这就是它执行缓慢的原因)。
The easiest way to fake clearing out pixels without having to do the offscreen bitmap buffering is to paint the background of your view over the image. 伪造清除像素而不必进行屏幕外位图缓冲的最简单方法是在图像上绘制视图的背景。
-(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];
}
}
The more difficult (but more correct) way is to do the image editing on a background serial queue in response to an editing event. 更困难(但更正确)的方法是在后台串行队列上进行图像编辑以响应编辑事件。 When you get a new action, you do the bitmap rendering in the background to an image buffer.
当您获得新动作时,可以在后台将位图渲染到图像缓冲区。 When the buffered image is ready, you call
setNeedsDisplay
to allow the view to be redrawn during the next update cycle. 当缓冲的映像准备就绪时,您调用
setNeedsDisplay
以允许在下一个更新周期中重绘视图。 This is more correct as drawRect:
should be displaying the content of your view as quickly as possible, not processing the editing action. 这更正确,因为
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
key是CGContextBeginTransparencyLayer并使用clearColor并设置CGContextSetBlendMode(context,kCGBlendModeClear);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.