使用贝塞尔曲线算法实现毛笔签名效果
2016-05-09 16:24
435 查看
最近项目中有个需要签名的地方,要用到手写签名,开始只是简单的实现手写签名,如图:
后来领导说,能不能实现像毛笔那样签名的效果,那好吧,领导说怎样就怎样吧,而且我也觉得这里用毛笔效果会更好些。那就只好运用贝塞尔曲线的原理了。实现如下:
最后呈上效果图:
后来领导说,能不能实现像毛笔那样签名的效果,那好吧,领导说怎样就怎样吧,而且我也觉得这里用毛笔效果会更好些。那就只好运用贝塞尔曲线的原理了。实现如下:
/** * This view implements the drawing canvas. * * It handles all of the input events and drawing functions. */ class PaintView extends View { private Paint paint; private Canvas cacheCanvas; private Bitmap cachebBitmap; private Path path; private List<TimePoint> mPoints = new ArrayList<>(); private float mVelocityFilterWeight; private float mLastTouchX; private float mLastTouchY; private float mLastVelocity; private float mLastWidth; private int mMinWidth; private int mMaxWidth; private RectF mDirtyRect; public Bitmap getCachebBitmap() { return cachebBitmap; } public void setSignatureBitmap(Bitmap signature) { clear(); ensureSignatureBitmap(); RectF tempSrc = new RectF(); RectF tempDst = new RectF(); int dWidth = signature.getWidth(); int dHeight = signature.getHeight(); int vWidth = getWidth(); int vHeight = getHeight(); // Generate the required transform. tempSrc.set(0, 0, dWidth, dHeight); tempDst.set(0, 0, vWidth, vHeight); Matrix drawMatrix = new Matrix(); drawMatrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.START); Canvas canvas = new Canvas(cachebBitmap); canvas.drawBitmap(signature, drawMatrix, null); // setIsEmpty(false); invalidate(); } public PaintView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SignaturePad, 0, 0); // Configurable parameters try { mMinWidth = a.getDimensionPixelSize( R.styleable.SignaturePad_minWidth, convertDpToPx(3)); mMaxWidth = a.getDimensionPixelSize( R.styleable.SignaturePad_maxWidth, convertDpToPx(12)); mVelocityFilterWeight = a.getFloat( R.styleable.SignaturePad_velocityFilterWeight, 0.6f); } finally { a.recycle(); } init(); } private void init() { paint = new Paint(); paint.setAntiAlias(true); paint.setStrokeJoin(Paint.Join.ROUND); paint.setStyle(Paint.Style.STROKE); paint.setColor(Color.BLACK); path = new Path(); cachebBitmap = Bitmap.createBitmap(p.width, (int) (p.height),// * // 0.8), Config.ARGB_8888); cacheCanvas = new Canvas(cachebBitmap); cacheCanvas.drawColor(Color.WHITE); mDirtyRect = new RectF(); mLastVelocity = 0; mLastWidth = (mMinWidth + mMaxWidth) / 2; path.reset(); invalidate(); } /** * Set the velocity filter weight. * * @param velocityFilterWeight * the weight. */ public void setVelocityFilterWeight(float velocityFilterWeight) { mVelocityFilterWeight = velocityFilterWeight; } private void ensureSignatureBitmap() { if (cachebBitmap == null) { cachebBitmap = Bitmap.createBitmap(p.width, (int) (p.height),// * // 0.8), Config.ARGB_8888); cacheCanvas = new Canvas(cachebBitmap); } } public void clear() { mPoints.clear(); mLastVelocity = 0; mLastWidth = (mMinWidth + mMaxWidth) / 2; path.reset(); if (cacheCanvas != null) { paint.setColor(BACKGROUND_COLOR); cacheCanvas.drawPaint(paint); paint.setColor(Color.BLACK); cacheCanvas.drawColor(Color.WHITE); invalidate(); } } @Override protected void onDraw(Canvas canvas) { // canvas.drawColor(BRUSH_COLOR); if (cachebBitmap != null) { if (topBitmap == null) { topBitmap = Bitmap.createBitmap( top_layout.getMeasuredWidth(), top_layout.getMeasuredHeight(), Config.ARGB_8888); top_layout.draw(new Canvas(topBitmap)); setSignatureBitmap(topBitmap); } canvas.drawBitmap(cachebBitmap, 0, 0, paint); // canvas.drawPath(path, paint); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { int curW = cachebBitmap != null ? cachebBitmap.getWidth() : 0; int curH = cachebBitmap != null ? cachebBitmap.getHeight() : 0; if (curW >= w && curH >= h) { return; } if (curW < w) curW = w; if (curH < h) curH = h; Bitmap newBitmap = Bitmap.createBitmap(curW, curH, Bitmap.Config.ARGB_8888); Canvas newCanvas = new Canvas(); newCanvas.setBitmap(newBitmap); if (cachebBitmap != null) { newCanvas.drawBitmap(cachebBitmap, 0, top_layout.getHeight(), null); } cachebBitmap = newBitmap; cacheCanvas = newCanvas; } @Override public boolean onTouchEvent(MotionEvent event) { if (!isEnabled()) return false; float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { getParent().requestDisallowInterceptTouchEvent(true); mPoints.clear(); mLastTouchX = x; mLastTouchY = y; path.moveTo(x, y); addPoint(new TimePoint(x, y)); } case MotionEvent.ACTION_MOVE: { // path.quadTo(cur_x, cur_y, x, y); resetDirtyRect(x, y); addPoint(new TimePoint(x, y)); break; } case MotionEvent.ACTION_UP: { // cacheCanvas.drawPath(path, paint); // path.reset(); // cur_x = x; // cur_y = y; resetDirtyRect(x, y); addPoint(new TimePoint(x, y)); getParent().requestDisallowInterceptTouchEvent(true); break; } default: return false; } // invalidate(); invalidate((int) (mDirtyRect.left - mMaxWidth), (int) (mDirtyRect.top - mMaxWidth), (int) (mDirtyRect.right + mMaxWidth), (int) (mDirtyRect.bottom + mMaxWidth)); return true; } public void addPoint(TimePoint point) { mPoints.add(point); if (mPoints.size() > 2) { if (mPoints.size() == 3) mPoints.add(0, mPoints.get(0)); ControlTimedPoints tmp = calculateCurveControlPoints( mPoints.get(0), mPoints.get(1), mPoints.get(2)); TimePoint c2 = tmp.c2; tmp = calculateCurveControlPoints(mPoints.get(1), mPoints.get(2), mPoints.get(3)); TimePoint c3 = tmp.c1; Bezier curve = new Bezier(mPoints.get(1), c2, c3, mPoints.get(2)); TimePoint startPoint = curve.startPoint; TimePoint endPoint = curve.endPoint; float velocity = endPoint.getSpeed(startPoint); velocity = Float.isNaN(velocity) ? 0.0f : velocity; velocity = mVelocityFilterWeight * velocity + (1 - mVelocityFilterWeight) * mLastVelocity; // The new width is a function of the velocity. Higher // velocities // correspond to thinner strokes. float newWidth = strokeWidth(velocity); // The Bezier's width starts out as last curve's final width, // and // gradually changes to the stroke width just calculated. The // new // width calculation is based on the velocity between the // Bezier's // start and end mPoints. addBezier(curve, mLastWidth, newWidth); mLastVelocity = velocity; mLastWidth = newWidth; // Remove the first element from the list, // so that we always have no more than 4 mPoints in mPoints // array. mPoints.remove(0); } } /** * Resets the dirty region when the motion event occurs. * * @param eventX * the event x coordinate. * @param eventY * the event y coordinate. */ private void resetDirtyRect(float eventX, float eventY) { // The mLastTouchX and mLastTouchY were set when the ACTION_DOWN // motion event occurred. mDirtyRect.left = Math.min(mLastTouchX, eventX); mDirtyRect.right = Math.max(mLastTouchX, eventX); mDirtyRect.top = Math.min(mLastTouchY, eventY); mDirtyRect.bottom = Math.max(mLastTouchY, eventY); } /** * * @param curve * @param startWidth * @param endWidth */ private void addBezier(Bezier curve, float startWidth, float endWidth) { ensureSignatureBitmap(); float originalWidth = paint.getStrokeWidth(); float widthDelta = endWidth - startWidth; float drawSteps = (float) Math.floor(curve.length()); for (int i = 0; i < drawSteps; i++) { // Calculate the Bezier (x, y) coordinate for this step. float t = ((float) i) / drawSteps; float tt = t * t; float ttt = tt * t; float u = 1 - t; float uu = u * u; float uuu = uu * u; float x = uuu * curve.startPoint.x; x += 3 * uu * t * curve.control1.x; x += 3 * u * tt * curve.control2.x; x += ttt * curve.endPoint.x; float y = uuu * curve.startPoint.y; y += 3 * uu * t * curve.control1.y; y += 3 * u * tt * curve.control2.y; y += ttt * curve.endPoint.y; // 设置画笔的大小 paint.setStrokeWidth(startWidth + ttt * widthDelta); //控制线显示的范围 if (y > top_layout.getHeight()) { cacheCanvas.drawPoint(x, y, paint); } expandDirtyRect(x, y); } paint.setStrokeWidth(originalWidth); } private ControlTimedPoints calculateCurveControlPoints(TimePoint s1, TimePoint s2, TimePoint s3) { float dx1 = s1.x - s2.x; float dy1 = s1.y - s2.y; float dx2 = s2.x - s3.x; float dy2 = s2.y - s3.y; TimePoint m1 = new TimePoint((s1.x + s2.x) / 2.0f, (s1.y + s2.y) / 2.0f); TimePoint m2 = new TimePoint((s2.x + s3.x) / 2.0f, (s2.y + s3.y) / 2.0f); float l1 = (float) Math.sqrt(dx1 * dx1 + dy1 * dy1); float l2 = (float) Math.sqrt(dx2 * dx2 + dy2 * dy2); float dxm = (m1.x - m2.x); float dym = (m1.y - m2.y); float k = l2 / (l1 + l2); TimePoint cm = new TimePoint(m2.x + dxm * k, m2.y + dym * k); float tx = s2.x - cm.x; float ty = s2.y - cm.y; return new ControlTimedPoints(new TimePoint(m1.x + tx, m1.y + ty), new TimePoint(m2.x + tx, m2.y + ty)); } private float strokeWidth(float velocity) { return Math.max(mMaxWidth / (velocity + 1), mMinWidth); } private int convertDpToPx(float dp) { return Math .round(dp * (getResources().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT)); } public Bitmap convertViewToBitmap(View view) { view.measure( MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); view.buildDrawingCache(); Bitmap bitmap = view.getDrawingCache(); return bitmap; } /** * Called when replaying history to ensure the dirty region includes all * mPoints. * * @param historicalX * the previous x coordinate. * @param historicalY * the previous y coordinate. */ private void expandDirtyRect(float historicalX, float historicalY) { if (historicalX < mDirtyRect.left) { mDirtyRect.left = historicalX; } else if (historicalX > mDirtyRect.right) { mDirtyRect.right = historicalX; } if (historicalY < mDirtyRect.top) { mDirtyRect.top = historicalY; } else if (historicalY > mDirtyRect.bottom) { mDirtyRect.bottom = historicalY; } } }
最后呈上效果图:
相关文章推荐
- TinyMCE v4用法
- sqlite清空数据表并使id值从1开始(sqlite默认id从1开始)
- idea16(2016.1.1)下载、安装、破解
- 面试汇总
- JS 页面跳转和刷新
- 72. Edit Distance【H】【65】
- bzoj 1880: [Sdoi2009]Elaxia的路线(拓扑排序+spfa)
- HDOJ(HDU) 2520 我是菜鸟,我怕谁(等差数列)
- nginx学习笔记三(nginx启动框架的处理流程)
- Activity的启动模式
- js事件监听器用法实例详解
- HDOJ(HDU) 2520 我是菜鸟,我怕谁(等差数列)
- 视频自动播放
- Day1作业1:登陆接口(加入日志、注册功能)
- Many to many relationships&Assignment
- POJ 1182 食物链 并查集
- QNX的音频应用实例
- STL:二级空间配置器浅谈
- Mysql 实现 Rownum() 排序后根据条件获取名次
- hdu 1598(最小生成树)