## Android绘制贝塞尔曲线

n阶贝兹曲线可如下推断。给定点P0P1Pn，其贝兹曲线即：

t 值的计算方式为：j/(N)N代表要生成的贝塞尔点个数，控制点的数量是n+1，手势里面通常我们可以取三个控制点，即n=2, 此时第j个贝塞尔点坐标为 B(t) = (1-t)^2*P0 + 2*(1-t)*t*P1 + t^2*P2 （j范围是[0, N]）

1、注册手势监听

``````@Override
public boolean onTouchEvent(final MotionEvent event) {

if (event != null) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
final float x, y;
switch (action) {
case MotionEvent.ACTION_DOWN:

handleActionDown(event);

break;
case MotionEvent.ACTION_POINTER_DOWN:

break;
case MotionEvent.ACTION_MOVE:
handleActionMove(event);
break;
caseMotionEvent.ACTION_POINTER_UP:
break;
case MotionEvent.ACTION_UP:

break;
}
return true;
}
return false;
}``````

2、然后处理ACTION_DOWNACTION_MOVE传过来的手势点：

``````void handleActionDown(MotionEvent event) {
scrawlDownX = event.getX();
scrawlDownY = event.getY();

points.clear();
PointF point = translateToGL(event.getX(), event.getY());

}

void handleActionMove(MotionEvent event) {
if (Math.abs(scrawlDownX - event.getX()) > VALID_SPACING
|| Math.abs(scrawlDownY - event.getY()) > VALID_SPACING) {
PointF point = translateToGL(event.getX(), event.getY());
unBindFBO();
}
}
}``````
`这里我们会根据每次传过来的点生成一个新的贝塞尔曲线点，然后将轨迹写入到蒙层里面，这边用opengl来保存轨迹，`
`接下来看看怎样生成贝塞尔曲线点：`
``````private void addBezierPoint(PointF point) {
if (points.size() == 3) {
points.remove(0);
}

if (points.size() == 2) {
PointF from = points.get(0);
PointF to = midPoint(points.get(0), points.get(1));
PointF control = midPoint(from, to);

calculateBezierCurve(from, to, control);
} else if (points.size() > 2) {
PointF from = midPoint(points.get(0), points.get(1));
PointF to = midPoint(points.get(1), points.get(2));
PointF control = points.get(1);

calculateBezierCurve(from, to, control);
}
}

private PointF midPoint(PointF p1, PointF p2) {
return new PointF((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}

/**
* 涂抹贝塞尔曲线两点之间插入的点数量，影响涂抹程度值
*/
private static final int BEZIER_INSET_POINT = 5;
/**
* 默认笔触大小
*/
private static final float DEFAULT_PEN_SIZE_RATIO = 20 / 750f;

/**
* 羽化的涂抹笔触，会比实际看过去的笔触小，所以此处需要有一个比例，使看过去涂抹的范围与笔触圆圈接近
*/
private static final float MASK_SCALE = 5 / 4f;

/**
* 笔触大小，比例值
*/
private float mPenSize = DEFAULT_PEN_SIZE_RATIO * 2 * MASK_SCALE;
/**
* 涂抹正方形区域比例
*/
private float mEraserRatio = mPenSize;

private void calculateBezierCurve(PointF from, PointF to, PointF control) {
int xNum = (int) ((to.x - from.x) / mEraserRatio * BEZIER_INSET_POINT);
int yNum = (int) ((to.y - from.y) / mEraserRatio * mHeight / mWidth * BEZIER_INSET_POINT);

// 坐标点的总数
int N = Math.max(Math.abs(xNum), Math.abs(yNum));
N = Math.max(N, 1);

final float[] array = new float[8 * N * 2];

float x, y, t;
for (int i = 0; i < N; i++) {
t = (float) i / N;

// 根据贝塞尔曲线函数，获得此时的x,y坐标
x = (1 - t) * (1 - t) * from.x + 2 * (1 - t) * t * control.x + t * t * to.x;
y = (1 - t) * (1 - t) * from.y + 2 * (1 - t) * t * control.y + t * t * to.y;

addVertex2Array(array, x, y, 16 * i);
}

// 一个点使用6个索引
final short[] indexArray = new short[N * 6];
for (int i = 0; i < N; i++) {
indexArray[i * 6] = (short) (4 * i);
indexArray[i * 6 + 1] = (short) (4 * i + 1);
indexArray[i * 6 + 2] = (short) (4 * i + 2);

indexArray[i * 6 + 3] = (short) (4 * i + 1);
indexArray[i * 6 + 4] = (short) (4 * i + 3);
indexArray[i * 6 + 5] = (short) (4 * i + 2);
}

mEraserFilter.initVertexData(array);
mEraserFilter.setIndexData(indexArray);
glViewport(0, 0, mWidth, mHeight);
unBindFBO();
}

private void addVertex2Array(float[] vertexArray, float x, float y, int index) {
vertexArray[index] = x - mEraserRatio;
vertexArray[index + 1] = -y + mEraserRatio * mWidth / mHeight;
vertexArray[index + 2] = 0f;
vertexArray[index + 3] = 1f;
vertexArray[index + 4] = x + mEraserRatio;
vertexArray[index + 5] = -y + mEraserRatio * mWidth / mHeight;
vertexArray[index + 6] = 1f;
vertexArray[index + 7] = 1f;
vertexArray[index + 8] = x - mEraserRatio;
vertexArray[index + 9] = -y - mEraserRatio * mWidth / mHeight;
vertexArray[index + 10] = 0f;
vertexArray[index + 11] = 0f;
vertexArray[index + 12] = x + mEraserRatio;
vertexArray[index + 13] = -y - mEraserRatio * mWidth / mHeight;
vertexArray[index + 14] = 1f;
vertexArray[index + 15] = 0f;
}
``````
##### 过程大概是：

1） 选取作为贝塞尔曲线公式里面参数的三个点，即起点、中点、目标点，为了使轨迹更平滑，起点的选择有两种情况，如果只有两个点，那么就选前一个点，如果超过了两个点，会选择前两个点的中点；然后目标点就是当前点和前一个点的中点

2） 将选择的三个点作为参数传给贝塞尔曲线公式，计算得出贝塞尔点

3） 为了使点更密集，可以根据起点和终点间的距离插入一定量的贝塞尔点

4） 利用opengl在生成的贝塞尔点位置处画矩形，因为点生成的速度和量都非常多，所以这边用到了drawElements这种快速绘制的函数，具体使用可以参考网上的样例

5） 经过上面操作就能将当前手势操作的轨迹画到蒙层里面，相当于保存了轨迹，然后想恢复的话可以用同样的操作擦除蒙层的轨迹，最终将蒙层映射到原图里面就实现了擦除功能

6） 注意写入FBO时是上下颠倒的，所以计算坐标时将y轴的值颠倒过来，实现绕x轴翻转的功能

#### 3、羽化

1） 修改橡皮檫对应的脚本：

``````precision highp float;
varying vec2 texcoordOut;

uniform float opacity;

void main(){
vec4 maskColor = vec4(1.0, 0.0, 0.0, 1.0);

gl_FragColor = maskColor *textureColor.r * opacity;

}``````

2） 设置叠加方式：

``````if (mMode == MODE_ERASER) {
glBlendFunc(GL_ONE, GL_ONE);
} else if (mMode== MODE_RECOVER) {
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE);
}``````

a)    如果是橡皮檫模式，我们的公式会变成

resultColor = srcColor * 1 + dstColor * 1;

b)    如果是恢复模式，我们的公式变成：

resultColor = dstColor *1 – srcColor * 1;

c)    利用上面两步操作后我们会生成一张橡皮檫轨迹的纹理，其中纹理上面的r值代表轨迹的透明度，然后我们将轨迹的纹理和素材原图的纹理做混合，混合结果中素材的透明度就取决于轨迹的r值。

d)    如果要设置多少步内擦除，可以设置变量：

``opacity = 1.0f/ BEZIER_INSET_POINT / mScrawlCount;``

3）注意这种方式绘制的贝塞尔曲线并不是严格的曲线，因为我们相当于拼接多条曲线，像手势操作这种我们不需要展示曲线给用户看，而且对效率要求比较高的情况下可以采用曲线拼接的方式。如果你要展示曲线给别人看就必须一次性画完一条曲线而不是一条条拼接而成