贝塞尔曲线----原理以及在Android平台的实现

一、什么是贝塞尔曲线?

贝塞尔曲线于 1962 年,由法国工程师皮埃尔·贝济埃(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。

贝塞尔曲线主要用于二维图形应用程序中的数学曲线,曲线由起始点,终止点(也称锚点)和控制点组成,通过调整控制点,通过一定方式绘制的贝塞尔曲线形状会发生变化。后面会具体介绍绘制的方法。

在计算机图形学中贝赛尔曲线的运用很广泛,例如Photoshop中的钢笔效果,Flash5的贝塞尔曲线工具,在软件GUI开发中一般也会提供对应的方法来实现贝赛尔曲线,我们熟知的CSS动画过渡时间函数也是通过贝塞尔曲线(三阶贝塞尔曲线)获取的。

二、贝塞尔曲线分为哪些类型?

贝塞尔曲线根据控制点的数量分为:

  • 一阶贝塞尔曲线(2 个控制点,其实就是一条直线)
  • 二阶贝塞尔曲线(3 个控制点)
  • 三阶贝塞尔曲线(4个控制点)
  • n阶贝塞尔曲线(n+1个控制点)

三、贝塞尔曲线有啥用?

  • QQ小红点拖拽效果
  • 电子书阅读的翻页效果
  • 波纹效果
  • 漂浮的爱心
  • ......

四、贝塞尔曲线是如何绘制出来的?

 一阶贝塞尔曲线 
一阶曲线是没有控制点的,仅有两个数据点(A 和 B),最终效果一个线段。 
动态过程可以参照下图(贝塞尔曲线相关的动态演示图片来自维基百科)。 
这里写图片描述

一阶曲线其实在Android的api里就是lineTo方法。

二阶贝塞尔曲线 
在平面内任选 3 个不共线的点,依次用线段连接。 

è¿éåå¾çæè¿°
在第一条线段上任选一个点 D。计算该点到线段起点的距离 AD,与该线段总长 AB 的比例。 

è¿éåå¾çæè¿°
连接这两点 DE。 

è¿éåå¾çæè¿°
从新的线段 DE 上再次找出相同比例的点 F,使得 DF:DE = AD:AB = BE:BC。

è¿éåå¾çæè¿°

到这里,我们就确定了贝塞尔曲线上的一个点 F。接下来,请稍微回想一下中学所学的极限知识,让选取的点 D 在第一条线段上从起点 A 移动到终点 B,找出所有的贝塞尔曲线上的点 F。所有的点找出来之后,我们也得到了这条贝塞尔曲线。

è¿éåå¾çæè¿°

动态过程如下: 

è¿éåå¾çæè¿° 

三阶贝塞尔曲线 
控制点个数为 4 时,就是三阶的曲线 

è¿éåå¾çæè¿°
步骤都是相同的,只不过我们每确定一个贝塞尔曲线上的点,要进行三轮取点操作。如图,AE:AB = BF:BC = CG:CD = EH:EF = FI:FG = HJ:HI,其中点 J 就是最终得到的贝塞尔曲线上的一个点。 

è¿éåå¾çæè¿°
这样我们得到的是一条三次贝塞尔曲线。 

è¿éåå¾çæè¿°
动态图如下: 
 è¿éåå¾çæè¿°
三阶曲线对应的方法是cubicTo

要绘制更复杂的曲线,控制点的增加也仅仅是线性的。这一特点使其不光在工业设计领域大展拳脚,就连数学基础不好的人也可以比较容易地掌握,比如大多数平面美术设计师们。 

五、Android上绘制贝塞尔曲线的API是啥?

 Android提供的有针对贝塞尔曲线绘制的api,不过它只提供了二阶和三阶的绘制方法,更高阶的贝塞尔曲线可以通过降阶的形式实现。这里分析一下二阶和三阶的绘制api:    

          Path.quadTo(x1,y1,x2,y2)

                  x1:控制点x坐标,y1:在控制点y坐标,x2:终点x坐标,y2:终点y坐标
          Path.rQuadTo(dx1,dy1,dx2,dy2)

                  dx1:控制点相对起点的x位移,dy1:控制点相对起点的y位,dx2:终点相对起点的x位移,dy2:终点相对起点的y位移

     这两个方法是绘制二阶贝塞尔曲线的,区别在于,控制点的坐标一个是绝对坐标,一个是相对位移。

      cubicTo(x1, y1, x2, y2, x3, y3)

             (x1,y1) 为控制点,(x2,y2)为控制点,(x3,y3) 为结束点。

      cubicTo(x1, y1, x2, y2, x3, y3)

             (x1,y1) 为控制点相对起点的位移,(x2,y2)为控制点相对起点的位移,(x3,y3) 为相对结束点的位移。

      这两个方法是绘制三阶贝塞尔曲线的,区别在于,控制点的坐标一个是绝对坐标,一个是相对位移。

六、贝塞尔曲线的代码实现

一阶曲线是一条线段,非常简单,不再进行介绍,都是path的基本用法。

二阶曲线: 
首先,两个数据点是控制贝塞尔曲线开始和结束的位置,而控制点则是控制贝塞尔的弯曲状态 
这里写图片描述

从上面的动态图可以看出,贝塞尔曲线在动态变化过程中有类似于橡皮筋一样的弹性效果,因此在制作一些弹性效果的时候很常用。

代码如下:

public class Bezier extends View {

    private Paint mPaint;
    private int centerX, centerY;

    private PointF start, end, control;

    public Bessel1(Context context) {
        super(context);
        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(8);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(60);

        start = new PointF(0,0);
        end = new PointF(0,0);
        control = new PointF(0,0);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w/2;
        centerY = h/2;

        // 初始化数据点和控制点的位置
        start.x = centerX-200;
        start.y = centerY;
        end.x = centerX+200;
        end.y = centerY;
        control.x = centerX;
        control.y = centerY-100;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 根据触摸位置更新控制点,并提示重绘
        control.x = event.getX();
        control.y = event.getY();
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 绘制数据点和控制点
        mPaint.setColor(Color.GRAY);
        mPaint.setStrokeWidth(20);
        canvas.drawPoint(start.x,start.y,mPaint);
        canvas.drawPoint(end.x,end.y,mPaint);
        canvas.drawPoint(control.x,control.y,mPaint);

        // 绘制辅助线
        mPaint.setStrokeWidth(4);
        canvas.drawLine(start.x,start.y,control.x,control.y,mPaint);
        canvas.drawLine(end.x,end.y,control.x,control.y,mPaint);

        // 绘制贝塞尔曲线
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(8);

        Path path = new Path();

        path.moveTo(start.x,start.y);
        path.quadTo(control.x,control.y,end.x,end.y);

        canvas.drawPath(path, mPaint);
    }
}

三阶曲线: 
三阶曲线由两个数据点和两个控制点来控制曲线状态。 
这里写图片描述

public class Bezier2 extends View {

    private Paint mPaint;
    private int centerX, centerY;

    private PointF start, end, control1, control2;
    private boolean mode = true;

    public Bezier2(Context context) {
        this(context, null);

    }

    public Bezier2(Context context, AttributeSet attrs) {
        super(context, attrs);

        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(8);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(60);

        start = new PointF(0, 0);
        end = new PointF(0, 0);
        control1 = new PointF(0, 0);
        control2 = new PointF(0, 0);
    }

    public void setMode(boolean mode) {
        this.mode = mode;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = h / 2;

        // 初始化数据点和控制点的位置
        start.x = centerX - 200;
        start.y = centerY;
        end.x = centerX + 200;
        end.y = centerY;
        control1.x = centerX;
        control1.y = centerY - 100;
        control2.x = centerX;
        control2.y = centerY - 100;

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 根据触摸位置更新控制点,并提示重绘
        if (mode) {
            control1.x = event.getX();
            control1.y = event.getY();
        } else {
            control2.x = event.getX();
            control2.y = event.getY();
        }
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //drawCoordinateSystem(canvas);

        // 绘制数据点和控制点
        mPaint.setColor(Color.GRAY);
        mPaint.setStrokeWidth(20);
        canvas.drawPoint(start.x, start.y, mPaint);
        canvas.drawPoint(end.x, end.y, mPaint);
        canvas.drawPoint(control1.x, control1.y, mPaint);
        canvas.drawPoint(control2.x, control2.y, mPaint);

        // 绘制辅助线
        mPaint.setStrokeWidth(4);
        canvas.drawLine(start.x, start.y, control1.x, control1.y, mPaint);
        canvas.drawLine(control1.x, control1.y,control2.x, control2.y, mPaint);
        canvas.drawLine(control2.x, control2.y,end.x, end.y, mPaint);

        // 绘制贝塞尔曲线
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(8);

        Path path = new Path();

        path.moveTo(start.x, start.y);
        path.cubicTo(control1.x, control1.y, control2.x,control2.y, end.x, end.y);

        canvas.drawPath(path, mPaint);
    }
} 

三阶曲线相比于二阶曲线可以制作更加复杂的形状,但是对于高阶的曲线,用低阶的曲线组合也可达到相同的效果,就是传说中的降阶。因此我们对贝塞尔曲线的封装方法一般最高只到三阶曲线。

 

下几篇博文会细说贝塞尔曲线在app上的应用~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值