7

My code is basically from this example (http://corner.squareup.com/2010/07/smooth-signatures.html) and Google APIs (FingerPaint) but now I want to use the class VelocityTracker in order to change the stroke width depending on the speed of my finger.

I thought I could split a path into smaller parts but I didn't find any examples. There's also this second post (http://corner.squareup.com/2012/07/smoother-signatures.html) but I do neither have a specific bezier curve class nor do I collect all the points in an ArrayList so their example for adjusting stroke width is not very helpful.

Does anyone have an idea how to handle this? I started to learn code two weeks ago so I'm pretty new in all this stuff.

Edit: I tried to implement the velocity of my MotionEvents and I used LogCat to track the current velocity while running the app. It did work out but when I tried to use the velocity as part of the parameter for mPaint.setStrokeWidth I did not get what I actually wanted. The width of the path I draw on my canvas was changing all the time from the moment I started drawing a line till I moved my finger up. So that's why I want to split a path into smaller parts because as it is now, only the last tracked velocity affects the strokeWidth.

public class SignatureView extends View {

    private static final String TAG = SignatureView.class.getSimpleName();
    private static final float STROKE_WIDTH = 10;
    private static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;
    private final double TOUCH_TOLERANCE = 5;
    private int h = getResources().getDisplayMetrics().heightPixels;
    private int w = getResources().getDisplayMetrics().widthPixels;

    private Path mPath = new Path();
    private Paint mPaint = new Paint();
    private Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG);
    private Bitmap mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    private Canvas mCanvas = new Canvas(mBitmap);

    private float mX, mY;
    private float lastTouchX, lastTouchY;
    private final RectF dirtyRect = new RectF();

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

    mPaint.setAntiAlias(true);
    mPaint.setColor(Color.BLACK);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeJoin(Paint.Join.ROUND);
    mPaint.setStrokeWidth(INITIAL_STROKE_WIDTH);

    Log.d(TAG, "TOUCH_TOLERANCE = " +TOUCH_TOLERANCE);
}

@Override
protected void onDraw(Canvas canvas) {
    canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);            
    canvas.drawPath(mPath, mPaint);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    float eventX = event.getX();
    float eventY = event.getY();
    int historySize = event.getHistorySize();

    switch (event.getAction()) {

        case MotionEvent.ACTION_DOWN:

            resetDirtyRect(eventX, eventY);
            mPath.reset();
            mPath.moveTo(eventX, eventY);
            mX = eventX;
            mY = eventY;
            break;

        case MotionEvent.ACTION_MOVE:

            float dx = Math.abs(eventX - mX);
            float dy = Math.abs(eventY - mY);

            if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {

                mPath.quadTo(mX, mY, (eventX + mX)/2, (eventY + mY)/2);
                mX = eventX;
                mY = eventY;
            }

            for (int i = 0; i < historySize; i++) {
                float historicalX = event.getHistoricalX(i);
                float historicalY = event.getHistoricalY(i);
                expandDirtyRect(historicalX, historicalY);
            }
            break;

        case MotionEvent.ACTION_UP:

            for (int i = 0; i < historySize; i++) {
                float historicalX = event.getHistoricalX(i);
                float historicalY = event.getHistoricalY(i);
                expandDirtyRect(historicalX, historicalY);
            }

            mPath.lineTo(mX, mY);
            mCanvas.drawPath(mPath, mPaint);
            mPath.reset();
            break;

        default:
            Log.d(TAG, "Ignored touch event: " + event.toString());
        return false;
    }

    // Include half the stroke width to avoid clipping.
        invalidate(     (int) (dirtyRect.left - HALF_STROKE_WIDTH),
                        (int) (dirtyRect.top - HALF_STROKE_WIDTH),
                        (int) (dirtyRect.right + HALF_STROKE_WIDTH),
                        (int) (dirtyRect.bottom + HALF_STROKE_WIDTH));

    lastTouchX = eventX;
    lastTouchY = eventY;
    return true;
}

private void expandDirtyRect(float historicalX, float historicalY) {
    if (historicalX < dirtyRect.left) {
        dirtyRect.left = historicalX;
    } else if (historicalX > dirtyRect.right) {
        dirtyRect.right = historicalX;
    }
    if (historicalY < dirtyRect.top) {
        dirtyRect.top = historicalY;
    } else if (historicalY > dirtyRect.bottom) {
        dirtyRect.bottom = historicalY;
    }
}

private void resetDirtyRect(float eventX, float eventY) {

    dirtyRect.left = Math.min(lastTouchX, eventX);
    dirtyRect.right = Math.max(lastTouchX, eventX);
    dirtyRect.top = Math.min(lastTouchY, eventY);
    dirtyRect.bottom = Math.max(lastTouchY, eventY);
}
}
Dave
  • 79
  • 1
  • 3
  • Have u manage to change the stroke width depending on the speed of my finger. Actually i am also facing the same issue in my code but not able to solve it yet. – AndroidDev Nov 13 '13 at 12:11

1 Answers1

2

You can use split your path object every time your stroke value changes depending on velocity. In you SignatureView class add

private Path mPath = new Path();
ArrayList<Path> mPaths = new ArrayList<Path>();

and take another ArrayList to keep stroke value for each path

ArrayList<int> strokes = new ArrayList<int>();

add a variable lastStroke along with lastTouchX and lastTouchY. I will recommend you to make lastStroke of type int.

private int lastStroke = -1; //give an initial value

now your onTouchEvent method should be something like this

@Override
public boolean onTouchEvent(MotionEvent event) {
    float eventX = event.getX();
    float eventY = event.getY();
    int historySize = event.getHistorySize();
    int eventStroke= //calculate stroke size with velocity and make it between 1-10 or any range you seem fit

    switch (event.getAction()) {

        case MotionEvent.ACTION_DOWN:

            resetDirtyRect(eventX, eventY);
            mPath.reset();
            mPath.moveTo(eventX, eventY);
            mX = eventX;
            mY = eventY;
            break;

        case MotionEvent.ACTION_MOVE:

            float dx = Math.abs(eventX - mX);
            float dy = Math.abs(eventY - mY);

            if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
                if(lastStroke != evetnStroke){
                    mPath = new Path();
                    mPath.moveTo(mX,mY);
                    mPaths.Add(mPath);
                    mStrokes.Add(eventStroke);
                }
                mPath.quadTo(mX, mY, (eventX + mX)/2, (eventY + mY)/2);
                mX = eventX;
                mY = eventY;
            }

            for (int i = 0; i < historySize; i++) {
                float historicalX = event.getHistoricalX(i);
                float historicalY = event.getHistoricalY(i);
                expandDirtyRect(historicalX, historicalY);
            }
            break;

        case MotionEvent.ACTION_UP:

            for (int i = 0; i < historySize; i++) {
                float historicalX = event.getHistoricalX(i);
                float historicalY = event.getHistoricalY(i);
                expandDirtyRect(historicalX, historicalY);
            }

           mPath.lineTo(mX, mY);
           break;

        default:
            Log.d(TAG, "Ignored touch event: " + event.toString());
        return false;
    }
    // Include half the stroke width to avoid clipping.
    invalidate(     (int) (dirtyRect.left - HALF_STROKE_WIDTH),
                    (int) (dirtyRect.top - HALF_STROKE_WIDTH),
                    (int) (dirtyRect.right + HALF_STROKE_WIDTH),
                    (int) (dirtyRect.bottom + HALF_STROKE_WIDTH));

    lastTouchX = eventX;
    lastTouchY = eventY;
    lastStroke = eventStroke;
    return true;
}

and your ondraw method would be

@Override
protected void onDraw(Canvas canvas) {
    for(int i=0; i<mPaths.size();i++){
        mPaint.setStrokeWidth(strokes.get(i));
        canvas.drawPath(mPaths.get(i), mPaint);
    }
}

this is the basic idea. you need to modify it to make it work.

th1rdey3
  • 4,176
  • 7
  • 30
  • 66
  • hey thanks for the answer! I think now I'm on the right track but there's still a problem. I guess it has to do with Android buffering touchpoints which are "too fast". I was trying to get them with a loop similar to the loops which are meant to expand the dirtyRect. But I always have gaps between the paths. I wanted to upvote you but my rep is too low, sorry! – Dave Mar 22 '13 at 13:55
  • I had the same problem. Then I modified the code so that a new path always starts at the last point of the previous path. For example if (x,y) is the last point of one path. Then the next path will move to (x,y) first. In android terms 'path.movrTo(x,y)' – th1rdey3 Mar 22 '13 at 17:09
  • @Dave Have u managed to draw your line smoothly...Actually am facing the same issue, can you look at this question i had posted in stackoverflow http://stackoverflow.com/questions/20560322/how-to-draw-path-with-variable-width-in-canvas – AndroidDev Dec 16 '13 at 07:21
  • @th1rdey3 how to get eventStroke value. – AndroidDev Dec 16 '13 at 08:02
  • You need to calculate stroke, there is no event for stroke. If you are using stylus input, then you can get pressure event value and use it to calculate stroke size. but for touches with finger the pressure event always returns the value `1`. – th1rdey3 Dec 16 '13 at 10:26
  • @th1rdey3 Thanks for your responce. Actually am using an stylus input and using the pressure value i am get variable width thickness, But the line drawn is not smooth can u look at the question and provide me a solution to make it work http://stackoverflow.com/questions/20610730/how-to-make-draw-path-smooth-while-drawing-with-variable-width-stroke – AndroidDev Dec 16 '13 at 12:25