【问题标题】:Bezier Curve drawing is very laggy贝塞尔曲线绘制非常滞后
【发布时间】:2016-06-05 05:23:33
【问题描述】:

所以这就是发生的事情: 当我画的时候,贝塞尔线非常平滑。我应用了制作控制点和端点的概念,以使平滑成为可能。但是,我找不到导致延迟的问题。

当我画画时,我会检查我的 CPU 使用率,它会在 5 秒内从大约 50% 变为 90%。我确保当我完成绘图时,我的点会被擦除,同时会创建一个缓冲区来创建我所绘制的图像。

我的猜测是在touchesMoved中同时绘制了太多点?必须有一些点被填充,程序可能无法处理。

#import "SmoothedBIView.h"

@implementation SmoothedBIView
{
    UIBezierPath *path;
    UIImage *incrementalImage;
    CGPoint pts[5]; // need to keep track of the four points of a Bezier segment and the first control point of the next segment
    uint ctr;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super initWithCoder:aDecoder])
    {
        [self setMultipleTouchEnabled:NO];
        [self setBackgroundColor:[UIColor whiteColor]];
        path = [UIBezierPath bezierPath];
        [path setLineWidth:2.0];
    }
    return self;

}
/* - (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setMultipleTouchEnabled:NO];
        path = [UIBezierPath bezierPath];
        [path setLineWidth:2.0];
    }
    return self;
}
*/


 animation.
- (void)drawRect:(CGRect)rect
{
    [incrementalImage drawInRect:rect];
    [path stroke];
}


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    ctr = 0;
    UITouch *touch = [touches anyObject];
    pts[0] = [touch locationInView:self];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint p = [touch locationInView:self];
    ctr++;
    pts[ctr] = p;
    if (ctr == 4)
    {
        pts[3] = CGPointMake((pts[2].x + pts[4].x)/2.0, (pts[2].y + pts[4].y)/2.0); // move the endpoint to the middle of the line joining the second control point of the first Bezier segment and the first control point of the second Bezier segment

        [path moveToPoint:pts[0]];
        [path addCurveToPoint:pts[3] controlPoint1:pts[1] controlPoint2:pts[2]]; // add a cubic Bezier from pt[0] to pt[3], with control points pt[1] and pt[2]

        [self setNeedsDisplay];
        // replace points and get ready to handle the next segment
        pts[0] = pts[3];
        pts[1] = pts[4];
        ctr = 1;
    }

}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self drawBitmap];
    /*[self setNeedsDisplay]; */
    [path removeAllPoints];
    ctr = 0;
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self touchesEnded:touches withEvent:event];
}

- (void)drawBitmap
{
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0.0);

    if (!incrementalImage)
    {
        UIBezierPath *rectpath = [UIBezierPath bezierPathWithRect:self.bounds];
        [[UIColor whiteColor] setFill];
        [rectpath fill];
    }
    [incrementalImage drawAtPoint:CGPointZero];
    [[UIColor blackColor] setStroke];
    [path stroke];
    incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
}

@end

【问题讨论】:

  • 当您说滞后时,您指的是恒定滞后(这是您的算法的自然结果,即每四个点绘制一次)?还是您指的是随着用户按住手指而变得越来越长的延迟(因为您仅在手势完成时才重建图像快照)。这两个都是问题,但我不确定你指的是哪个。
  • 也许切换到CGPathCGImage 会更有效,但除非你测量,否则很难说。
  • @Rob 这是一个持续的延迟。
  • 那么我个人 (a) 不会等到 ctr 变为 4,而是在每次触摸时更新路径; (b) 使用预测性触摸。我也会考虑使用CAShapeLayer,因为据称这比简单的drawRect 实现更快。

标签: ios objective-c uibezierpath


【解决方案1】:

这里有几个问题:

  1. 如果您担心此算法遭受的持续滞后,一个问题是您仅在计数器为 4 时才更新路径(并且您始终落后于至少一个点),这将夸大滞后。您可以更频繁地更新路径,正如这里所讨论的,https://stackoverflow.com/a/34997902/1271826,这是一个与您的非常相似的 Swift 实现。

    基本思想是,与其等到计数器达到 4,不如在计数器为 1 时画一条线,在计数器为 2 时画四边形曲线,在计数器为 3 时画三次曲线,然后当计数器为 4 时绘制修改后的三次曲线。这减少了您的算法将遭受的​​一般滞后。

  2. 您可以通过使用预测性触摸来减少感知迟滞。它并没有解决问题,(并且使算法稍微复杂化,因为您必须考虑在真正的触摸最终进入时退出先前预测的触摸的概念),但它可以进一步降低感知滞后。请参阅https://stackoverflow.com/a/34583708/1271826,了解如何使用预测性触摸的示例(结合 Catmull-Rom 和 Hermite 样条,另一种平滑算法)。对于 Swift 参考,我深表歉意,但如果您搜索 Objective-C 的预测性接触,我怀疑您也会找到很多示例。

  3. 如果您担心贝塞尔曲线过长会导致延迟,与其等到触摸结束再生成快照,不如在固定次数的触摸后进行,即使是在手势中间。这将防止您的贝塞尔曲线变得太长以至于您开始遇到性能问题。是的,您不希望在绘图手势中间做一些计算密集型的事情,但您必须在某个时候画一条线(不是双关语)。

  4. 有一个论点是使用CAShapeLayer 作为贝塞尔路径而不是自己绘制它。我看到它表明这比简单的 drawRect 实现更优化,但我承认我从未对它进行基准测试。

【讨论】:

    【解决方案2】:

    提高性能的一个简单方法是使用 setNeedsDisplayInRect: 代替 setNeedsDisplay。您当前正在重绘整个视图,而不仅仅是已更改的区域。

    【讨论】:

    • 我尝试过在贝塞尔曲线方面无法确定要放入参数中的点。据我所知,setNeedsDisplayInRect 仅适用于正常点。
    • 曲线将完全绘制在由 2 个端点和 2 个控制点定义的框内。所以 minX = MIN(a.x, b.x, c1.x, c2.x), minY = MIN(a.y, b.y, c1.y, c2.y), maxX = 等等。由于贝塞尔曲线会在曲线的中间描边,因此您可以添加线条宽度的缓冲区。
    猜你喜欢
    • 1970-01-01
    • 2011-02-26
    • 1970-01-01
    • 2014-09-29
    • 2017-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多