使用贝塞尔曲线算法实现毛笔签名效果

最近项目中有个需要签名的地方,要用到手写签名,开始只是简单的实现手写签名,如图:

后来领导说,能不能实现像毛笔那样签名的效果,那好吧,领导说怎样就怎样吧,而且我也觉得这里用毛笔效果会更好些。那就只好运用贝塞尔曲线的原理了。实现如下:

/**
     * 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;
            }
        }
    }

最后呈上效果图:

时间: 2024-10-16 03:05:01

使用贝塞尔曲线算法实现毛笔签名效果的相关文章

贝塞尔曲线算法

前言 在google找到一篇个人认为最通俗的贝塞尔曲线实现算法博文. 我转载了该博文图片和公式. 出处: http://blog.csdn.net/jimi36/article/details/7792103 图片 一次贝塞尔. 二次贝塞尔. 三次贝塞尔. 贝塞尔曲线算法,布布扣,bubuko.com

贝塞尔曲线算法,js贝塞尔曲线路径点

//anchorpoints:贝塞尔基点 //pointsAmount:生成的点数 //return 路径点的Array function CreateBezierPoints(anchorpoints, pointsAmount) { var points = []; for (var i = 0; i < pointsAmount; i++) { var point = MultiPointBezier(anchorpoints, i / pointsAmount); points.push

paintcode生成贝塞尔曲线相关代码实现动画效果

// // ViewController.m // paintCodeTestOC // // Created by LongMa on 2019/7/25. // #import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIButton *btn; @property(nonatomic, strong) UIBezierPath *gPath; @end

【转】贝塞尔曲线介绍

原文链接: http://blog.csdn.net/sangxiaonian/article/details/51984013 http://blog.csdn.net/sangxiaonian/article/details/51984584 http://blog.csdn.net/sangxiaonian/article/details/51985405 其他参考链接: https://www.jianshu.com/p/55c721887568 作为一个有只志向的码农,除了知道一些基本

把商品添加到购物车的动画效果(贝塞尔曲线)

如图: 参考: Android补间动画,属性动画实现购物车添加动画 思路: 确定动画的起终点 在起终点之间使用二次贝塞尔曲线填充起终点之间的点的轨迹 设置属性动画,ValueAnimator插值器,获取中间点的坐标 将执行动画的控件的x.y坐标设为上面得到的中间点坐标 开启属性动画 当动画结束时的操作 难点: PathMeasure的使用 - getLength() - boolean getPosTan(float distance, float[] pos, float[] tan) 的理解

【转】三次贝塞尔曲线绘制算法

原文:http://www.cnblogs.com/flash3d/archive/2012/01/30/2332176.html 源码:http://files.cnblogs.com/flash3d/bezier.rar ==================================================== 这学期学图形学,就把自己的一些粗浅的理解发上去让大家拍砖.前些天做三次贝塞尔曲线绘制的上机练习,正好将从直线扫描算法中启发得来的n次多项式批量计算用上了,自认为优化得还

购物车特效-贝塞尔曲线动画(点击添加按钮的进候,产生抛物线动画效果)

demo效果: l 购物车特效原理: 1.从添加按钮获取开始坐标 2.从购物车图标获取结束坐标 3.打气一个视图,添加属性动画ObjectAnimator(缩小),ValueAnimator(路线) 4.动画开始时添加该视图,动画结束删除该视图 5.运动路径使用TypeEvaluator与贝塞尔函数计算 activity布局: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" an

浅谈属性动画简单使用之实现爱的贝塞尔曲线浪漫告白效果(三)

谁说程序员不浪漫的啊,每次看到别人在黑程序员心中就有一种无奈,只是他们看到的是程序员不好的一面,今天我将用这个案例告诉那些人,程序猿也是一个很浪漫,很有情调的人.在程序员心中他们只想做最高效的事情,没有什么比效率更重要了.那就开始今天程序猿的告白之旅. 我们都知道属性动画有个强大的地方,它实现让某个控件按照我们指定的运动轨迹来运动.也就是说它可以按照一个抛物线来运动,也可以按照一个线性的线来运动,还可以按照我们今天所讲的贝塞尔曲线的轨迹来运动.为什么他可以按照某一个轨迹来运动呢??首先我们来分析

贝塞尔曲线实现购物车飞入效果

代码地址如下:http://www.demodashi.com/demo/12618.html 前言 做了一个模仿添加物品飞入购物车效果的例子,下面来讲讲它的简单使用 将涉及到以下内容: 工具类的使用 项目结构图与效果图 程序设计与实现 一. 工具类设计 工具类比较多代码,这里就不每个都贴出来了,如下截图为工具类 二. 工具类的使用 飞入购物车效果我封装成了一个工具类FlyAnimtor,下面讲讲它的使用. 如果你想使用飞入效果,你可以这样写: FlyAnimtor.getInstance().