【问题标题】:Drawing a self-erasing path with CGContextRef使用 CGContextRef 绘制自擦除路径
【发布时间】:2017-02-08 23:53:24
【问题描述】:

我想在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 中运行的成本。

有没有更好的方法来绘制一条自擦除线?

奖励:我真正想要的是“缩小并消失”的路径。换句话说,在延迟之后,而不是仅仅清除描边路径,我想让它从 5pt 缩小到 0pt,同时淡出不透明度。

【问题讨论】:

  • 创建一个图形上下文,在其中绘制并捕获图像是一种非常昂贵的方法。我建议改为使用 UIView 的自定义子类并覆盖 drawRect。然后,您可以直接在当前图形上下文中执行绘图命令。您可以在视图上发出 setNeedsDisplay 以触发要更新的视图。您可能希望在擦除阶段比绘制阶段稍微增加笔画宽度(尝试大 1/2 点)。
  • @DuncanC 谢谢。是的,我刚刚意识到增加笔画宽度可以解决鬼轮廓问题。我会试试你的 drawInRect 建议。干杯!
  • 擦除策略不是一个好主意......如果你画一个循环排序的东西,新线穿过旧线,旧线被擦除,新线将被擦除为好在路口。
  • 不是 drawInRect,drawRect()。当需要更新其内容时,这是在自定义视图上调用的方法。实际上,您最好在每次更新时以新状态绘制整个视图,而不是尝试擦除旧的路径段。 (@spinalwrap 关于自交叉路径的观点是您在使用擦除方法时将面临的问题之一。)
  • 如果我这样做,我想我会使用 CAShapeLayer 并对其进行动画更改。

标签: ios objective-c uiimageview cgcontext


【解决方案1】:

我会让视图以 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];

所以在每次绘制调用时,创建一个新的贝塞尔路径,移动到第一个点,然后将线条添加到所有其他点。最后,画线。

要实现“收缩”线,您可以在数组中连续的点对之间绘制短线,并使用 age 属性来计算笔划宽度。这并不完美,因为各个段在起点和终点的宽度相同,但这是一个起点。

重要提示:如果你要画很多点,性能将成为一个问题。这种使用 Quartz 的路径渲染并没有完全调整为真正快速渲染。事实上,它非常非常慢。

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

如果您遇到性能问题并希望继续此项目,请查看 OpenGL 渲染。将纯 C 结构推送到 GPU 中,您将能够更快地运行此程序。

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多