码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2016-05-12 20:48:53      阅读:846      评论:0      收藏:0      [点我收藏+]

标签:

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

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

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

最后呈上效果图:
技术分享

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

标签:

原文地址:http://blog.csdn.net/ongyunhai/article/details/51354020

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!