美文网首页
OC之贝塞尔曲线上的点移动

OC之贝塞尔曲线上的点移动

作者: 苏沫离 | 来源:发表于2018-09-18 14:32 被阅读0次

贝塞尔曲线就是依据四个任意位置的点坐标绘制出的一条光滑曲线。在历史上,研究贝塞尔曲线的人最初是按照已知曲线参数方程来确定四个点的思路设计出这种矢量曲线绘制法。贝塞尔曲线一般方程为:

贝塞尔曲线一般方程.jpg

这里用到的是三阶贝塞尔曲线,三次方公式为:

三阶贝塞尔曲线方程.jpg

P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝兹曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”。

我们可以调用下述方法绘制三阶贝塞尔曲线:

void CGPathAddCurveToPoint(CGMutablePathRef cg_nullable path,
    const CGAffineTransform * __nullable m, CGFloat cp1x, CGFloat cp1y,
    CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y)

需求效果如下图所示:

贝塞尔曲线上一个点.gif

关键代码为:

- (void)drawRect:(CGRect)rect{
    CGPoint startPoint = CGPointMake(0, 470 / 570.0 * CGRectGetHeight(rect));
    CGPoint controlPoint1 = CGPointMake(300 / 750.0 * CGRectGetWidth(rect),200 / 570.0 * CGRectGetHeight(rect));
    CGPoint controlPoint2 = CGPointMake(500 / 750.0 * CGRectGetWidth(rect), 600 / 570.0 * CGRectGetHeight(rect));
    CGPoint endPoint = CGPointMake(CGRectGetWidth(rect), 190 / 570.0 * CGRectGetHeight(rect));
    
    CGContextRef context = UIGraphicsGetCurrentContext();

    /****** 贝塞尔曲线及填充渐变色 ****/
    CGFloat locations[] = {0.0, 1.0};
    NSArray *colors = @[(__bridge id) [UIColor colorWithRed:70/255.0 green:95/255.0 blue:251/255.0 alpha:0.1].CGColor, (__bridge id) [UIColor colorWithRed:70/255.0 green:95/255.0 blue:251/255.0 alpha:1].CGColor];
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations);
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, -3, CGRectGetHeight(rect));
    CGPathAddLineToPoint(path, NULL, -3, startPoint.y);
    CGPathAddCurveToPoint(path, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x + 3, endPoint.y);
    CGPathAddLineToPoint(path, NULL,CGRectGetWidth(rect) + 3, CGRectGetHeight(rect));
    CGPathCloseSubpath(path);
    CGRect pathRect = CGPathGetBoundingBox(path);
    CGContextAddPath(context, path);
    CGContextSetStrokeColorWithColor(context, UIColor.clearColor.CGColor);
    CGContextDrawLinearGradient(context, gradient, CGPointMake(CGRectGetMinX(pathRect), CGRectGetMidY(pathRect)), CGPointMake(CGRectGetMaxX(pathRect), CGRectGetMaxY(pathRect)), 0);
    CGColorSpaceRelease(colorSpace);
    CGPathRelease(path);
    CGContextSetLineWidth(context, 3);
    CGContextSetLineCap(context, kCGLineCapRound);
    CGContextSetLineJoin(context, kCGLineJoinRound);
    CGContextSetStrokeColorWithColor(context, UIColor.whiteColor.CGColor);
    CGContextStrokePath(context);
    
    
    if (_intimacy > 0 && _intimacy < 1){
        CGFloat t = get_t_value_By_binarySort(_intimacy * CGRectGetWidth(rect), startPoint, controlPoint1, controlPoint2, endPoint);
        CGPoint point = getCubicBezierPathThePoint(t, startPoint, controlPoint1, controlPoint2, endPoint);
        
        /****** 当前所在位置点 ****/
        UIImage *pointImage = [UIImage imageNamed:@"Image1"];
        CGRect pointImageFrame = CGRectMake(point.x - pointImage.size.width / 2.0, point.y - pointImage.size.height/2.0, pointImage.size.width, pointImage.size.height);
        [pointImage drawInRect:pointImageFrame];
        CGContextSetLineWidth(context,1.0f);
        CGContextSetStrokeColorWithColor(context,[UIColor colorWithRed:255/255.0 green:255/255.0 blue:255/255.0 alpha:0.26].CGColor);
        CGFloat lengths[] = {4,2};
        CGContextSetLineDash(context,0, lengths,2);
        CGContextMoveToPoint(context,point.x,point.y);
        CGContextAddLineToPoint(context,point.x,CGRectGetHeight(rect));
        CGContextStrokePath(context);

        /****** 当前所在位置文字显示 ****/
        UIImage *textImage = [UIImage imageNamed:@"Image"];
        CGRect imageFrame = CGRectMake(point.x - textImage.size.width / 2.0, point.y - textImage.size.height - 6, textImage.size.width, textImage.size.height);
        [textImage drawInRect:imageFrame];
        NSString *progress = [NSString stringWithFormat:@"%.0f",_intimacy * 100];
        NSDictionary *attributes = @{NSFontAttributeName : [UIFont systemFontOfSize:15],NSForegroundColorAttributeName:[UIColor colorWithRed:64/255.0 green:88/255.0 blue:237/255.0 alpha:1]};
        CGSize textSize = [progress boundingRectWithSize:CGSizeMake(100, 100) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;
        [progress drawAtPoint:CGPointMake(point.x - textSize.width / 2.0, imageFrame.origin.y + 8) withAttributes:attributes];
    }

    /*************** 日期 ******************/
    NSArray<NSString *> *array = @[@"10",@"20",@"30",@"40",@"50",@"60",@"70",@"80",@"90"];
    [array enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

        CGPoint textPoint = CGPointMake((idx + 1) * (CGRectGetWidth(rect) / (array.count + 1)), CGRectGetHeight(rect) - 20);
        [obj drawAtPoint:CGPointMake(textPoint.x - 4, textPoint.y) withAttributes:@{NSFontAttributeName : [UIFont systemFontOfSize:8],NSForegroundColorAttributeName:[UIColor.whiteColor colorWithAlphaComponent:0.43]}];

        CGContextSetLineWidth(context, 1);
        CGContextSetStrokeColorWithColor(context, [UIColor.whiteColor colorWithAlphaComponent:0.5].CGColor);
        CGContextMoveToPoint(context, textPoint.x, CGRectGetHeight(rect) - 4);
        CGContextAddLineToPoint(context, textPoint.x,CGRectGetHeight(rect));
        CGContextStrokePath(context);
    }];
}

绘制一个贝塞尔曲线并不是一件困难的事情,但是我们要实现 “根据任意 x 坐标,计算出贝塞尔曲线上的某一点” 是一件比较复杂的事情,因为在框架库 <CoreGraphics/CoreGraphics.h> 中并没有一个函数可以根据任意 x 坐标,计算出该贝塞尔曲线上的某一点!我们需要根据三阶贝塞尔曲线方程算出相对应的点的坐标,代码如下:

/* 获取三阶贝塞尔曲线方程上的某一点
 *
 * float t 指定的x坐标比例,取值范围是 0 ~ 1 之间
 * CGPoint startPoint 起始点
 * CGPoint controlPoint1 控制点1
 * CGPoint controlPoint2 控制点2
 * CGPoint endPoint 结束点
 * 返回值:返回指定x坐标的贝塞尔曲线上的某一点
 */
CGPoint getCubicBezierPathThePoint(const float t,const CGPoint startPoint,const CGPoint controlPoint1,const CGPoint controlPoint2,const CGPoint endPoint){
    CGPoint point = CGPointZero;
    float temp = 1 - t;
    point.x = startPoint.x * temp * temp * temp + 3 * controlPoint1.x * t * temp * temp + 3 * controlPoint2.x * t * t * temp + endPoint.x * t * t * t;
    point.y = startPoint.y * temp * temp * temp + 3 * controlPoint1.y * t * temp * temp + 3 * controlPoint2.y * t * t * temp + endPoint.y * t * t * t;
    return point;
}

我们可以根据该函数计算出给定 t 值的贝塞尔曲线方程的某一点,但是我们现实需求是:根据 x 坐标来计算出对应的 y 坐标;也就是我们需要首先根据指定的 x 坐标来计算出对应的 t 值,再根据 t 值来计算出 对应的 y 坐标,在这里笔者使用的二分法(由果推出因)计算合适的 t 值:

/* 二分法求取 t 值
 *
 * float x 指定的x坐标
 * CGPoint startPoint 起始点
 * CGPoint controlPoint1 控制点1
 * CGPoint controlPoint2 控制点2
 * CGPoint endPoint 结束点
 * 返回值:返回指定x坐标的贝塞尔曲线方程的 t 值
 */
CGFloat get_t_value_By_binarySort(const float x,const CGPoint startPoint,const CGPoint controlPoint1,const CGPoint controlPoint2,const CGPoint endPoint)
{
    float a = 0.0 , b = 1.0;
    float xa = getCubicBezierPathThePoint(a, startPoint, controlPoint1, controlPoint2, endPoint).x;
    float xb = getCubicBezierPathThePoint(b, startPoint, controlPoint1, controlPoint2, endPoint).x;
    float xt = getCubicBezierPathThePoint((b + a) / 2.0, startPoint, controlPoint1, controlPoint2, endPoint).x;
    //x 的取值误差范围在 0.1
    while (fabsf(x - xt) > 0.1) {
        if (x < xt && x > xa){
            b = (b + a) / 2.0;
            xb = xt;
            xt = getCubicBezierPathThePoint((b + a) / 2.0, startPoint, controlPoint1, controlPoint2, endPoint).x;
        } else if (x > xt && x < xb) {
            a = (b + a) / 2.0;
            xa = xt;
            xt = getCubicBezierPathThePoint((b + a) / 2.0, startPoint, controlPoint1, controlPoint2, endPoint).x;
        } else {
            break;
        }
    }
    return (b + a) / 2.0;
}

该二分法有一定的误差,但是对于人的肉眼来说,误差范围在 0.1 之内,很难识别!因此笔者在这里设定误差范围为 0~0.1。

Demo 下载

相关文章

  • OC之贝塞尔曲线上的点移动

    贝塞尔曲线就是依据四个任意位置的点坐标绘制出的一条光滑曲线。在历史上,研究贝塞尔曲线的人最初是按照已知曲线参数方程...

  • flutter贝塞尔曲线

    1.贝塞尔曲线 2.绘制贝塞尔曲线 1.要绘制贝塞尔线,我们需要四个点:起点,终点和两个控制点,如下图所示。移动控...

  • 自带美感的贝塞尔曲线原理与实战——Android自定义UI

    目录一、前言二、贝塞尔曲线的绘制规则三、在canvas中如何绘制贝塞尔曲线四、实战五、写在最后 一、前言 贝塞尔曲...

  • 贝塞尔曲线

    二阶贝塞尔曲线为了确定曲线上的一个点,需要进行两轮取点的操作,因此我们称得到的贝塞尔曲线为二次曲线. 使用三阶贝塞...

  • iOS 贝塞尔曲线上所有点的获取

    最近在项目中用到了贝塞尔曲线画图,关于贝塞尔曲线的用法网上相应的文章很多,在此不再赘述,但是获取贝塞尔曲线上所有的...

  • 三阶贝塞尔曲线拟合1/4圆

    根据贝塞尔曲线的知识,我们知道三阶贝塞尔曲线的参数方程如下,其中A、B、C、D为四个控制点坐标,P(t)表示曲线上...

  • 自定义View-2Path贝塞尔曲线

    Path贝塞尔曲线 贝塞尔曲线用途广泛 QQ消息小红点拖拽效果炫酷的下拉控件翻书效果 一阶曲线是没有控制点的,只有...

  • 贝塞尔曲线及Path类详解(1)

    贝塞尔曲线 贝塞尔曲线是计算机图形图像造型的基本工具,是图形造型运用得最多的基本线条之一。它通过控制曲线上的四个点...

  • 购物车动画

    使用贝塞尔曲线实现购物车抛物线动画关键步骤 使用path 构建贝塞尔曲线 使用PathMeasure计算曲线上每个...

  • 初试 贝塞尔曲线

    Android 绘图贝塞尔曲线简单使用 在Android中某些自定义View的时候需要绘制某些曲线,这时候贝塞尔曲...

网友评论

      本文标题:OC之贝塞尔曲线上的点移动

      本文链接:https://www.haomeiwen.com/subject/kpiunftx.html