使用 CGContextRef 绘制自擦除路径
Drawing a self-erasing path with CGContextRef
我想在 UIImageView,
上绘制一个 "disappearing stroke",它会在触摸事件之后在固定时间延迟后自动擦除。这是我 ViewController.
中的内容
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
CGPoint lp = lastPoint;
UIColor *color = [UIColor blackColor];
[self drawLine:5 from:lastPoint to:currentPoint color:color blend:kCGBlendModeNormal];
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self drawLine:brush from:lp to:currentPoint color:[UIColor clearColor] blend:kCGBlendModeClear];
});
lastPoint = currentPoint;
}
- (void)drawLine:(CGFloat)width from:(CGPoint)from to:(CGPoint)to color:(UIColor*)color blend:(CGBlendMode)mode {
UIGraphicsBeginImageContext(self.view.frame.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
CGContextMoveToPoint(context, from.x, from.y);
CGContextAddLineToPoint(context, to.x, to.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, width);
CGContextSetStrokeColorWithColor(context, [color CGColor]);
CGContextSetBlendMode(context, mode);
CGContextStrokePath(context);
self.tempDrawImage.image = UIGraphicsGetImageFromCurrentImageContext();
[self.tempDrawImage setAlpha:1];
UIGraphicsEndImageContext();
}
绘制阶段运行良好,但随后的擦除阶段存在一些问题。
- 虽然 "fill" 行被正确清除,但路径周围的细笔画仍然存在。
- "erase phase" 起伏不定,远不及绘图阶段那么流畅。我最好的猜测是,这是由于
UIGraphicsBeginImageContext
运行 在 dispatch_after
. 中的成本
有没有更好的画自擦线的方法?
奖励:我真正想要的是通往 "shrink and vanish." 的路径 换句话说,在延迟之后,我希望将它从 5pt 缩小到0pt 同时淡出不透明度。
我会让视图以 60 Hz 的频率连续绘制,每次使用存储在数组中的点绘制整条线。这样,如果你从数组中删除最旧的点,它们将不再被绘制。
要连接您的视图以显示刷新率 (60 Hz),试试这个:
displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
存储每个点的年龄属性,然后遍历数组并删除比阈值更早的点。
例如
@interface AgingPoint <NSObject>
@property CGPoint point;
@property NSTimeInterval birthdate;
@end
// ..... later, in the draw call
NSTimeInterval now = CACurrentMediaTime();
AgingPoint *p = [AgingPoint new];
p.point = touchlocation; // get yr touch
p.birthdate = now;
// remove old points
while(myPoints.count && now - [myPoints[0] birthdate] > 1)
{
[myPoints removeObjectAtIndex: 0];
}
myPoints.add(p);
if(myPoints.count < 2)
return;
UIBezierPath *path = [UIBezierPath path];
[path moveToPoint: [myPoints[0] point]];
for (int i = 1; i < myPoints.count; i++)
{
[path lineToPoint: [myPoints[i] point];
}
[path stroke];
所以在每次绘制调用时,创建一个新的贝塞尔曲线路径,移动到第一个点,然后向所有其他点添加线条。最后划线。
要实现 "shrinking" 线,您可以在数组中连续的点对之间绘制短线,并使用年龄 属性 来计算笔画宽度。这并不完美,因为各个线段在起点和终点的宽度相同,但这是一个起点。
重要提示:如果你要画很多点,性能就会成为一个问题。这种使用 Quartz 的路径渲染并没有经过精确调整以实现真正快速的渲染。事实上,它非常非常慢。
Cocoa数组和对象也不是很快。
如果您 运行 遇到性能问题并且想继续这个项目,请查看 OpenGL 渲染。通过将纯 C 结构推入 GPU,您将能够更快地 运行。
这里有很多很棒的答案。我认为理想的解决方案是使用 OpenGL,因为它必然是性能最高的,并且在精灵、轨迹和其他有趣的视觉效果方面提供最大的灵活性。
我的应用程序是某种远程控制器,旨在简单地提供小型视觉辅助来跟踪运动,而不是留下持久或高保真笔画。因此,我最终创建了一个 UIView
的简单子类,它使用 CoreGraphics 绘制 UIBezierPath
。我最终会用 OpenGL 解决方案替换这个 quick-fix 解决方案。
我使用的实现远非完美,因为它留下了干扰未来笔画的白色路径,直到用户抬起他们的触摸,这会重置 canvas。我已经发布了我使用的解决方案 here,以防有人觉得它有用。
我想在 UIImageView,
上绘制一个 "disappearing stroke",它会在触摸事件之后在固定时间延迟后自动擦除。这是我 ViewController.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
CGPoint lp = lastPoint;
UIColor *color = [UIColor blackColor];
[self drawLine:5 from:lastPoint to:currentPoint color:color blend:kCGBlendModeNormal];
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self drawLine:brush from:lp to:currentPoint color:[UIColor clearColor] blend:kCGBlendModeClear];
});
lastPoint = currentPoint;
}
- (void)drawLine:(CGFloat)width from:(CGPoint)from to:(CGPoint)to color:(UIColor*)color blend:(CGBlendMode)mode {
UIGraphicsBeginImageContext(self.view.frame.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
CGContextMoveToPoint(context, from.x, from.y);
CGContextAddLineToPoint(context, to.x, to.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, width);
CGContextSetStrokeColorWithColor(context, [color CGColor]);
CGContextSetBlendMode(context, mode);
CGContextStrokePath(context);
self.tempDrawImage.image = UIGraphicsGetImageFromCurrentImageContext();
[self.tempDrawImage setAlpha:1];
UIGraphicsEndImageContext();
}
绘制阶段运行良好,但随后的擦除阶段存在一些问题。
- 虽然 "fill" 行被正确清除,但路径周围的细笔画仍然存在。
- "erase phase" 起伏不定,远不及绘图阶段那么流畅。我最好的猜测是,这是由于
UIGraphicsBeginImageContext
运行 在dispatch_after
. 中的成本
有没有更好的画自擦线的方法?
奖励:我真正想要的是通往 "shrink and vanish." 的路径 换句话说,在延迟之后,我希望将它从 5pt 缩小到0pt 同时淡出不透明度。
我会让视图以 60 Hz 的频率连续绘制,每次使用存储在数组中的点绘制整条线。这样,如果你从数组中删除最旧的点,它们将不再被绘制。
要连接您的视图以显示刷新率 (60 Hz),试试这个:
displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
存储每个点的年龄属性,然后遍历数组并删除比阈值更早的点。
例如
@interface AgingPoint <NSObject>
@property CGPoint point;
@property NSTimeInterval birthdate;
@end
// ..... later, in the draw call
NSTimeInterval now = CACurrentMediaTime();
AgingPoint *p = [AgingPoint new];
p.point = touchlocation; // get yr touch
p.birthdate = now;
// remove old points
while(myPoints.count && now - [myPoints[0] birthdate] > 1)
{
[myPoints removeObjectAtIndex: 0];
}
myPoints.add(p);
if(myPoints.count < 2)
return;
UIBezierPath *path = [UIBezierPath path];
[path moveToPoint: [myPoints[0] point]];
for (int i = 1; i < myPoints.count; i++)
{
[path lineToPoint: [myPoints[i] point];
}
[path stroke];
所以在每次绘制调用时,创建一个新的贝塞尔曲线路径,移动到第一个点,然后向所有其他点添加线条。最后划线。
要实现 "shrinking" 线,您可以在数组中连续的点对之间绘制短线,并使用年龄 属性 来计算笔画宽度。这并不完美,因为各个线段在起点和终点的宽度相同,但这是一个起点。
重要提示:如果你要画很多点,性能就会成为一个问题。这种使用 Quartz 的路径渲染并没有经过精确调整以实现真正快速的渲染。事实上,它非常非常慢。
Cocoa数组和对象也不是很快。
如果您 运行 遇到性能问题并且想继续这个项目,请查看 OpenGL 渲染。通过将纯 C 结构推入 GPU,您将能够更快地 运行。
这里有很多很棒的答案。我认为理想的解决方案是使用 OpenGL,因为它必然是性能最高的,并且在精灵、轨迹和其他有趣的视觉效果方面提供最大的灵活性。
我的应用程序是某种远程控制器,旨在简单地提供小型视觉辅助来跟踪运动,而不是留下持久或高保真笔画。因此,我最终创建了一个 UIView
的简单子类,它使用 CoreGraphics 绘制 UIBezierPath
。我最终会用 OpenGL 解决方案替换这个 quick-fix 解决方案。
我使用的实现远非完美,因为它留下了干扰未来笔画的白色路径,直到用户抬起他们的触摸,这会重置 canvas。我已经发布了我使用的解决方案 here,以防有人觉得它有用。