2

I am using the following line of code to draw a path on a Canvas. So far everything works fine and I can easily draw path using this code.

But now our requirement is to draw a path with variable width, means the path user draw is based on the pressure applied by the user, I mean to say if the user applied light pressure the path will be thin and if the user applied high pressure the path will be thick and so on. So far I succeeded in drawing a path with variable width also, but the line drawn is not smooth. Why it is happening? Is anything I miss in my code?

Help me to sort this out.

Code that I used for Drawing path with one Width

 public class FingerPaint extends GraphicsActivity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this));
    }

    public void colorChanged(int color) 
    {

    }

    public class MyView extends View 
    {
        private static final float STROKE_WIDTH = 5f;       

        private Paint paint = new Paint();
        
        private Path mPath = new Path();
        ArrayList<Path> mPaths = new ArrayList<Path>();
        
        ArrayList<Integer> mStrokes = new ArrayList<Integer>();

        private float lastTouchX;
        private float lastTouchY;
        private final RectF dirtyRect = new RectF();
        
        private int lastStroke = -1;
        int variableWidthDelta = 0;
        
        private float           mX, mY;
        
        private static final float       TOUCH_TOLERANCE = 4;

        public MyView(Context context) 
        {
            super(context);

            paint.setAntiAlias(true);
            paint.setDither(true);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeJoin(Paint.Join.ROUND);
            paint.setStrokeCap(Paint.Cap.ROUND);    
            paint.setStrokeWidth(STROKE_WIDTH);
        }

        public void clear()
        {
            mPath.reset();
            // Repaints the entire view.
            invalidate();
        }

        @Override
        protected void onDraw(Canvas canvas) 
        {
            for(int i=0; i<mPaths.size();i++)
            {
                paint.setStrokeWidth(mStrokes.get(i));
                canvas.drawPath(mPaths.get(i), paint);
            }
        }
    
        @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:
                {                   
                    if (event.getPressure()>=0.00 && event.getPressure()<0.05)
                    {
                        variableWidthDelta = -2;
                    }
                    else if (event.getPressure()>=0.05 && event.getPressure()<0.10)
                    {
                        variableWidthDelta = -2;
                    }
                    else if (event.getPressure()>=0.10 && event.getPressure()<0.15)
                    {
                        variableWidthDelta = -2;
                    }
                    else if (event.getPressure()>=0.15 && event.getPressure()<0.20)
                    {
                        variableWidthDelta = -2;
                    }
                    else if (event.getPressure()>=0.20 && event.getPressure()<0.25)
                    {
                        variableWidthDelta = -2;
                    }
                    else if (event.getPressure() >= 0.25 && event.getPressure()<0.30)
                    {
                        variableWidthDelta = 1;
                    }
                    else if (event.getPressure() >= 0.30 && event.getPressure()<0.35)
                    {
                        variableWidthDelta = 2;
                    }
                    else if (event.getPressure() >= 0.35 && event.getPressure()<0.40)
                    {
                        variableWidthDelta = 3;
                    }
                    else if (event.getPressure() >= 0.40 && event.getPressure()<0.45)
                    {
                        variableWidthDelta = 4;
                    }
                    else if (event.getPressure() >= 0.45 && event.getPressure()<0.60)
                    {
                        variableWidthDelta = 5;
                    }
                    
                    float dx = Math.abs(eventX - mX);
                    float dy = Math.abs(eventY - mY);

                    if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE)
                    {
                        if(lastStroke != variableWidthDelta)
                        {   
                            mPath.lineTo(mX, mY);
                            
                            mPath = new Path();
                            mPath.moveTo(mX,mY);
                            mPaths.add(mPath);
                            mStrokes.add(variableWidthDelta);
                        }
                        
                        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;
                }
            }
            
            // Include half the stroke width to avoid clipping.
            invalidate();

            lastTouchX = eventX;
            lastTouchY = eventY;
            lastStroke = variableWidthDelta;
            
            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;
            }
        }

        /**
         * Resets the dirty region when the motion event occurs.
         */
        private void resetDirtyRect(float eventX, float eventY) 
        {
            // The lastTouchX and lastTouchY were set when the ACTION_DOWN
            // motion event occurred.
            dirtyRect.left = Math.min(lastTouchX, eventX);
            dirtyRect.right = Math.max(lastTouchX, eventX);
            dirtyRect.top = Math.min(lastTouchY, eventY);
            dirtyRect.bottom = Math.max(lastTouchY, eventY);
        }
    }
}

enter image description here

iknow
  • 8,358
  • 12
  • 41
  • 68
AndroidDev
  • 4,521
  • 24
  • 78
  • 126

5 Answers5

2

Path does not support variable width drawing.

You can get the touch pressure and velocity using MotionEvent.getPressure() and VelocityTracker.computeCurrentVelocity() methods.

Next, you need to create a function to map a particular velocity or pressure to a certain width.

Assuming you have these ready, One way to draw variable width paths would be to divide a path into multiple paths each of a different width. For instance, if width needs to progress from 10 to 50 over the course of a single path, you can have 10 paths instead of width 5,10,15,20... and so on. You will have to do a lot of optimization as creating a large number of Path objects will lead to memory consumption.

Another way is to use quad-curves or bezier curves.

Anup Cowkur
  • 20,443
  • 6
  • 51
  • 84
  • It's not mine. It's based on Cocos2D for iOS. You can use it to study the algorithm and methodology.You can't directly use the same code. – Anup Cowkur Dec 13 '13 at 06:43
1

You can use getPressure() method and combine it with this answer https://stackoverflow.com/a/15533607/1112882 to make it work. Idea is to keep storing widths and using it.

Community
  • 1
  • 1
M-Wajeeh
  • 17,204
  • 10
  • 66
  • 103
  • I go through this code earlier, but not able to work this out inside my code. Can you please help me with some basic steps – AndroidDev Dec 13 '13 at 06:42
  • Hey i made some changes in my code. its working but the line drawn are not smooth.; – AndroidDev Dec 15 '13 at 11:03
  • Well here are few suggestions. 1) Make `mStrokes` a `Float` `ArrayList`. 2) You can remove all if else and can just assign like this `variableWidthDelta = 5 * event.getPressure();` for smoother transitions and then add it to `mStrokes`. – M-Wajeeh Dec 15 '13 at 13:57
  • I try what u said, but still the problem is there. I updated my question with an screenshot, please check it once – AndroidDev Dec 16 '13 at 07:06
0

Update: Thanks to Raghunandan and M-WaJeEh.

I was wrong. You can refer to this SO question.

android find pressure on screen

And the other link in the comments section.

http://developer.android.com/reference/android/view/MotionEvent.html#getPressure(int)

Community
  • 1
  • 1
Kumar Bibek
  • 9,016
  • 2
  • 39
  • 68
  • what about this http://stackoverflow.com/questions/9312518/android-find-pressure-on-screen – Raghunandan Dec 13 '13 at 06:27
  • Thats not true @Kumar. You can use `getPressure()` method. http://developer.android.com/reference/android/view/MotionEvent.html#getPressure(int) – M-Wajeeh Dec 13 '13 at 06:30
  • Yep, I was wrong. Never knew this API existed. Will edit my answer. – Kumar Bibek Dec 13 '13 at 06:31
  • BTW there is no sensor involved, All it does is try to track how many pixels are in contacts with finger and decides pressure based on that. – M-Wajeeh Dec 13 '13 at 06:35
0
@Override
    protected synchronized void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        this.canvas = canvas;
        Log.d("tag", "Padding" + padding);
        int newPadding = (int) padding;

        // Set Min X
        int xPadding = (int) padding;
        Paint bottomLeftTextPaint = new Paint();
        Typeface tf = Typeface.create("Helvetica", Typeface.NORMAL);
        bottomLeftTextPaint.setColor(context.getResources().getColor(
                R.color.common_color_rangeseekbar_bottomtext));
        bottomLeftTextPaint.setTextAlign(Align.LEFT);
        bottomLeftTextPaint.setTypeface(tf);
        bottomLeftTextPaint.setTextSize(20);

        bottomLeftTextPaint.setColor(context.getResources().getColor(
                R.color.common_color_rangeseekbar_bottomtext));
        canvas.drawText(absoluteMinValue + "", newPadding - 5,
                0.5f * (getHeight() + lineHeight) + 30, bottomLeftTextPaint);
        // draw seek bar background line
        final RectF rect = new RectF(newPadding - 5,
                0.5f * (getHeight() - lineHeight), getWidth() - padding,
                0.5f * (getHeight() + lineHeight));
        paint.setStyle(Style.FILL);
        // paint.setColor(Color.parseColor("#ED797F"));
        paint.setColor(Color.parseColor("#e2e2e2"));
        paint.setAntiAlias(true);
        canvas.drawRect(rect, paint);
        RectF rectStartLine = new RectF(newPadding - 5,
                0.5f * (getHeight() - lineHeight) - 5, padding,
                0.5f * (getHeight() + lineHeight) + 5);
        paint.setStyle(Style.FILL);
        paint.setColor(Color.BLACK);
        paint.setAntiAlias(true);
        canvas.drawRect(rectStartLine, paint);
        // End Line
        // RectF rectEndLine = new RectF(getWidth() - padding,
        // 0.5f * (getHeight() - lineHeight) - 5,
        // getWidth() - padding + 5, 0.5f * (getHeight() + lineHeight) + 5);
        RectF rectEndLine = new RectF(getWidth() - padding,
                0.5f * (getHeight() - lineHeight) - 5,
                getWidth() - padding + 5, 0.5f * (getHeight() + lineHeight) + 5);
        paint.setStyle(Style.FILL);
        paint.setColor(Color.BLACK);
        paint.setAntiAlias(true);
        canvas.drawRect(rectEndLine, paint);
        // End Text
        // Set Min X
        int xEndPadding = (int) padding;

        paint.setColor(Color.parseColor(context.getResources().getString(
                R.color.common_color_rangeseekbar_bottomtext)));
        paint.setTextSize(20);
        int max = (Integer) absoluteMaxValue;
        String MaxValue = String.valueOf(max);
        paint.setColor(Color.parseColor(context.getResources().getString(
                R.color.common_color_rangeseekbar_bottomtext)));
        Paint bottomTextPaint = new Paint();
        bottomTextPaint.setTypeface(tf);
        bottomTextPaint.setColor(context.getResources().getColor(
                R.color.common_color_rangeseekbar_bottomtext));
        bottomTextPaint.setTextAlign(Align.RIGHT);
        bottomTextPaint.setTypeface(tf);
        bottomTextPaint.setTextSize(20);
        // if (MaxValue.length() > 4) {
        //
        // canvas.drawText(absoluteMaxValue + "", getWidth() - padding - 23,
        // 0.5f * (getHeight() + lineHeight) + 30, bottomTextPaint);
        // } else if (MaxValue.length() > 3) {
        // canvas.drawText(absoluteMaxValue + "", getWidth() - padding - 18,
        // 0.5f * (getHeight() + lineHeight) + 30, bottomTextPaint);
        // } else if (MaxValue.length() > 2) {
        // canvas.drawText(absoluteMaxValue + "", getWidth() - padding - 13,
        // 0.5f * (getHeight() + lineHeight) + 30, bottomTextPaint);
        // } else {
        canvas.drawText(absoluteMaxValue + "", getWidth() - padding,
                0.5f * (getHeight() + lineHeight) + 30, bottomTextPaint);
        // }
        // draw seek bar active range line
        rect.left = normalizedToScreen(normalizedMinValue);
        rect.right = normalizedToScreen(normalizedMaxValue);

        // orange color
        paint.setColor(DEFAULT_COLOR);
        paint.setTypeface(tf);

        canvas.drawRect(rect, paint);
        Paint headerPaint = new Paint();
        // Set TextSize
        headerPaint.setTextSize(20);
        headerPaint.setTextAlign(Align.LEFT);
        headerPaint.setTypeface(tf);
        headerPaint.setColor(Color.parseColor(context.getResources().getString(
                R.color.common_color_rangeseekbar_toptext)));
        headerPaint.setTextAlign(Align.LEFT);
        // draw minimum thumb
        drawThumb(normalizedToScreen(normalizedMinValue),
                Thumb.MIN.equals(pressedThumb), canvas);
        canvas.drawText("" + getSelectedMinValue(),
                normalizedToScreen(normalizedMinValue)-5,
                (float) ((0.5f * getHeight()) - thumbHalfHeight) - 8,
                headerPaint);
        // draw maximum thumb
        drawThumb(normalizedToScreen(normalizedMaxValue),
                Thumb.MAX.equals(pressedThumb), canvas);
        // Right TrackText
        Paint righText = new Paint();
        righText.setTextAlign(Align.RIGHT);
        righText.setAntiAlias(true);
        righText.setTextSize(20);
        righText.setTypeface(tf);
        canvas.drawText("" + getSelectedMaxValue(),
                normalizedToScreen(normalizedMaxValue),
                (float) ((0.5f * getHeight()) - thumbHalfHeight) , righText);
    }
dipali
  • 10,966
  • 5
  • 25
  • 51
0

Maybe this will help you, had to implement one myself.

MatrixDev
  • 1,432
  • 15
  • 20