Monday 25 October 2010

Filtering onTouch for light pressure

For the drawing program I am writing, there is a sensitivity problem when the pressure is light on the touchpad. When the finger is pressed down (like when you are in the middle of a drag operation) the touch point is stable as the area of the finger pressed to the touchpad is large and so the centroid of the area is quite stable.

However at the end of the drag operation, as the finger pulls away, the area on the touch sensor decreases and the actual touch point seems to vary a lot. This is a problem for me in my drawing program, as you make fine changes to the drawing, you don't want the changes to be affected by this noise as you pull up from the touchpad.

This solution uses a mean filter to combat the problem - noting new there. But we combine the mean value with the pressure value so as the pressure gets lighter, we us more of the mean value. It works best if the finger pulls off the surface slowly.

The filter class I have used is shown below:
/*
* licensed under CC BY-SA : http://creativecommons.org/licenses/by-sa/3.0/legalcode
*/
class MeanFilter {
 public static final int DEFAULT_LEN = 11;
 float[][] points = new float[2][DEFAULT_LEN];
 int currentPos=0;
 int actualValues = 0;
 int length = DEFAULT_LEN;
 float pressureLimit = 0.2f;
  
 public MeanFilter(int length,float pressureLimit) {
  super();
  this.length = length;
  this.pressureLimit = pressureLimit;
 }
 public void add(float x,float y) {
  points[0][currentPos]=x;
  points[1][currentPos]=y;
  currentPos++;
  currentPos%=length;
  if (actualValues<length) {
   actualValues++;
   actualValues=Math.min(actualValues, length);
  }
 }
 public void clear() {currentPos=0;actualValues=0;}
 public void get(PointF defaultPoint,float pressure){
  if (actualValues==0) {return;}
  if (pressure<pressureLimit) {
   float xmean = getMean(points[0], actualValues);
   float ymean = getMean(points[1], actualValues);
   float pressureRatio = pressure/pressureLimit;
   defaultPoint.x=(pressureRatio)*defaultPoint.x+(1-pressureRatio)*xmean;
   defaultPoint.y=(pressureRatio)*defaultPoint.y+(1-pressureRatio)*ymean;
  }
 }
 public void setLength(int i) {
  length = i;
  clear();
 } 
 private float getMean(float[] arr,int ctr){
  float accum = 0;
  for (int i=0;i<ctr;i++) {
   accum+=arr[i];
  }
  return accum/ctr;
 }
}

To use it we create an instance - this is a mean filter length:7, pressure threshold:0.3
private MeanFilter touchFilter = new MeanFilter(7,0.3f);

Then in onTouch, we can filter the value to be used like so:
/*
* licensed under CC BY-SA : http://creativecommons.org/licenses/by-sa/3.0/legalcode
*/
public boolean onTouch(View v, MotionEvent event) {
 PointF touchPointOnScreen = new PointF(event.getX(),event.getY());
 switch (event.getAction() ) {
  case MotionEvent.ACTION_DOWN: touchFilter.clear();
   break;
  case MotionEvent.ACTION_MOVE: 
   touchFilter.add(event.getX(),event.getY());
   if (event.getPressure()<0.05) {return true;}
   if (event.getPressure()<0.3) {
    touchFilter.get(touchPointOnScreen,event.getPressure());
   } 
   break;
  case MotionEvent.ACTION_UP:
   touchFilter.get(touchPointOnScreen,event.getPressure());
   break;
 }
 // use touchPointOnScreen here...
 return true;
}
So touchPointOnScreen contains the filtered value to use.

We can note that the mean values arent calculated until the pressure drops below the threshold, thus being slightly more efficient than just mean filtering all the time, this also stops touchpoint lag as the mean filtered value will generally lay somewhere behind the current value.

This code is made open under the CC BY-SA licence.

No comments:

Post a Comment