使用CGContextRef绘制自我擦除路径

时间:2017-02-08 23:53:25

标签: ios objective-c uiimageview cgcontext

我想画一个"消失的中风"在UIImageView,上跟随触摸事件并在固定时间延迟后自行擦除。这是我在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();
}

绘制阶段效果很好,但随后的擦除阶段存在一些问题。

  1. 虽然该行"填充"如果被正确清除,路径周围会留下薄薄的笔划。
  2. "擦除阶段"波涛汹涌,远不如绘图阶段那么光滑。我最好的猜测是,这是由于UIGraphicsBeginImageContextdispatch_after的费用。
  3. 有没有更好的方法来绘制自我擦除线?

    奖励:我真正喜欢的是通往"收缩和消失的道路。"换句话说,在延迟之后,我不想仅仅清除描边路径,而是希望它从5pt缩小到0pt同时淡出不透明度。

2 个答案:

答案 0 :(得分:1)

我只是让视图以60 Hz连续绘制,每次使用存储在数组中的点绘制整条线。这样,如果从阵列中删除最旧的点,则不再绘制它们。

连接视图以显示刷新率(60 Hz),试试这个:

displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

存储年龄属性以及每个点,然后循环遍历数组并删除超过阈值的点。

e.g。

@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];

所以在每次绘制调用时,创建一个新的bezierpath,移动到第一个点,然后向所有其他点添加行。最后,划线。

实施&#34;收缩&#34;你可以在数组的连续点之间绘制一条短线,并使用age属性计算笔触宽度。这并不完美,因为各个段在起点和终点都有相同的宽度,但这是一个开始。

重要提示:如果您要提取大量积分,性能将成为一个问题。这种使用Quartz的路径渲染并没有完全调整为快速渲染。事实上,它非常非常慢。

Cocoa数组和对象也不是很快。

如果遇到性能问题并且想要继续此项目,请查看OpenGL渲染。通过将普通的C结构推入GPU,您将能够更快地运行。

答案 1 :(得分:0)

这里有很多很棒的答案。我认为理想的解决方案是使用OpenGL,因为它不可避免地是性能最高的,并且在精灵,轨迹和其他有趣的视觉效果方面提供最大的灵活性。

我的应用程序是各种遥控器,旨在简单地提供一个小的视觉辅助来跟踪运动,而不是留下持久或高保真的笔画。因此,我最终创建了一个UIView的简单子类,它使用CoreGraphics绘制UIBezierPath。我最终会用OpenGL解决方案替换这个快速修复解决方案。

我使用的实现远非完美,因为它留下了干扰未来笔画的白色路径,直到用户抬起触摸,重置画布。我已经发布了我使用的解决方案here,以防有人发现它有用。