I created a sample drawing app where user can draw using variable width stroke, So far drawing path with variable stroke is working, but the lines drawn are not smooth. The code i used to achieve that is shown below.
我创建了一个示例绘图应用程序,用户可以使用可变宽度笔画绘制,到目前为止,绘制可变的笔画的路径是工作的,但是绘制的线条不平滑。我用来实现的代码如下所示。
Help me to sort this out as m stuck on this from last two days.
请帮我解决这两天的问题。
Code to draw path using variable stroke 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 static final float STROKE_DELTA = 0.0001f; // for float comparison
private static final float STROKE_INCREMENT = 0.01f; // amount to interpolate
private float currentStroke = STROKE_WIDTH;
private float targetStroke = STROKE_WIDTH;
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;
}
// if current not roughly equal to target
if( Math.abs(targetStroke - currentStroke) > STROKE_DELTA )
{
// move towards target by the increment
if( targetStroke > currentStroke)
{
currentStroke = Math.min(targetStroke, currentStroke + STROKE_INCREMENT);
}
else
{
currentStroke = Math.max(targetStroke, currentStroke - STROKE_INCREMENT);
}
}
mStrokes.add((int) currentStroke);
targetStroke = variableWidthDelta;
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);
}
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);
}
}
}
Output result that i got
输出结果。
Output that i want to achieve
我想要实现的输出。
2 个解决方案
#1
3
Instead of jumping immediately to a new stroke width when you detect a change, you could set a target and interpolate toward it until you reach it. Your mStrokes
would need to be Float
s instead of Integer
s.
当你检测到一个变化时,你可以设定一个目标,并在到达它之前对它进行插值,而不是立即跳到一个新的描边宽度。你的笔画需要是浮点数而不是整数。
private static final float STROKE_DELTA = 0.0001f; // for float comparison
private static final float STROKE_INCREMENT = 0.01f; // amount to interpolate
private float currentStroke = STROKE_WIDTH;
private float targetStroke = STROKE_WIDTH;
Where you currently create a new path for a new stroke width, do something like this:
在当前为新的笔画宽度创建新路径的地方,请执行以下操作:
// if current not roughly equal to target
if( Math.abs(targetStroke - currentStroke) > STROKE_DELTA ) {
// move towards target by the increment
if( targetStroke > currentStroke )
currentStroke = Math.min(targetStroke, currentStroke + STROKE_INCREMENT);
else
currentStroke = Math.max(targetStroke, currentStroke - STROKE_INCREMENT);
mPath.lineTo(mX, mY);
mPath = new Path();
mPath.moveTo(mX,mY);
mPaths.add(mPath);
mStrokes.add(currentStroke);
}
You would update targetStroke
where you currently set variableWidthDelta
.
您将更新targetStroke,其中您当前设置了variableWidthDelta。
#2
0
Your current code translates pretty much every touch move event (above a certain delta distance) into a new path segment. To make a smooth path, you need to do some processing on this large number of initial points that the user touched, and turn it into a smaller number of quad or cubic path segments.
您当前的代码将几乎所有的触摸移动事件(超过一定的delta距离)转换为新的路径段。为了使路径平滑,您需要对用户所接触的大量初始点进行一些处理,并将其转换为少量的quad或立方路径段。
Have a look at the demo on this page: http://paperjs.org/tutorials/paths/smoothing-simplifying-flattening/#simplifying-paths and the corresponding simplification code: https://github.com/paperjs/paper.js/blob/master/src/path/PathFitter.js
看看这个页面上的演示:http://paperjs.org/tutorials/paths/- simplifing - simplifing/ # simplifingpath和相应的简化代码:https://github.com/paperjs/paper.js/blob/master/src/path/PathFitter.js ?
Obviously it's in JavaScript not Java, but you'll need to use a similar algorithm. An added complication will be that you will probably have to break down your smoothed path segments into multiple sub-segments again after smoothing to support varying stroke width within a single segment.
显然它是JavaScript而不是Java,但您需要使用类似的算法。另外一个复杂的问题是,您可能需要在平滑后将平滑的路径段分割成多个子段,以支持单个段中不同的笔画宽度。
#1
3
Instead of jumping immediately to a new stroke width when you detect a change, you could set a target and interpolate toward it until you reach it. Your mStrokes
would need to be Float
s instead of Integer
s.
当你检测到一个变化时,你可以设定一个目标,并在到达它之前对它进行插值,而不是立即跳到一个新的描边宽度。你的笔画需要是浮点数而不是整数。
private static final float STROKE_DELTA = 0.0001f; // for float comparison
private static final float STROKE_INCREMENT = 0.01f; // amount to interpolate
private float currentStroke = STROKE_WIDTH;
private float targetStroke = STROKE_WIDTH;
Where you currently create a new path for a new stroke width, do something like this:
在当前为新的笔画宽度创建新路径的地方,请执行以下操作:
// if current not roughly equal to target
if( Math.abs(targetStroke - currentStroke) > STROKE_DELTA ) {
// move towards target by the increment
if( targetStroke > currentStroke )
currentStroke = Math.min(targetStroke, currentStroke + STROKE_INCREMENT);
else
currentStroke = Math.max(targetStroke, currentStroke - STROKE_INCREMENT);
mPath.lineTo(mX, mY);
mPath = new Path();
mPath.moveTo(mX,mY);
mPaths.add(mPath);
mStrokes.add(currentStroke);
}
You would update targetStroke
where you currently set variableWidthDelta
.
您将更新targetStroke,其中您当前设置了variableWidthDelta。
#2
0
Your current code translates pretty much every touch move event (above a certain delta distance) into a new path segment. To make a smooth path, you need to do some processing on this large number of initial points that the user touched, and turn it into a smaller number of quad or cubic path segments.
您当前的代码将几乎所有的触摸移动事件(超过一定的delta距离)转换为新的路径段。为了使路径平滑,您需要对用户所接触的大量初始点进行一些处理,并将其转换为少量的quad或立方路径段。
Have a look at the demo on this page: http://paperjs.org/tutorials/paths/smoothing-simplifying-flattening/#simplifying-paths and the corresponding simplification code: https://github.com/paperjs/paper.js/blob/master/src/path/PathFitter.js
看看这个页面上的演示:http://paperjs.org/tutorials/paths/- simplifing - simplifing/ # simplifingpath和相应的简化代码:https://github.com/paperjs/paper.js/blob/master/src/path/PathFitter.js ?
Obviously it's in JavaScript not Java, but you'll need to use a similar algorithm. An added complication will be that you will probably have to break down your smoothed path segments into multiple sub-segments again after smoothing to support varying stroke width within a single segment.
显然它是JavaScript而不是Java,但您需要使用类似的算法。另外一个复杂的问题是,您可能需要在平滑后将平滑的路径段分割成多个子段,以支持单个段中不同的笔画宽度。