I want to implement a canvas drawing application with custom brush & undo/redo operation. First of all my code works perfectly without using the custom brush (including the undo/redo operation). According to this answer How to make custom brush for canvas in android? I used simple image spikes for bitmap draw.
Now the issue are,
The undo, redo operation doesn't works, custom brush paints over and over again whenever moving the touch points.
Q: How to make the undo/redo operation work?
The custom brush stokers aren't smooth as they should. Right now they looks rough and artificial.
Q. How to make the painting smooth and natural with custom brush strokes?
Check my sample code here,
public class DrawingView extends View {
private Context ctx;
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Path> undonePaths = new ArrayList<Path>();
private Map<Path, Float> brushMap = new HashMap<Path, Float>();
private Map<Path, List<Vector2>> customBrushMap = new HashMap<Path, List<Vector2>>();
private Bitmap mBitmapBrush;
private Vector2 mBitmapBrushDimensions;
private List<Vector2> mPositions = new ArrayList<Vector2>(100);
private boolean isCustomBrush = false;
private int selectedColor;
private float brushSize, lastBrushSize;
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private Path drawPath;
private Paint drawPaint, canvasPaint;
private int paintColor = 0xFF660000, paintAlpha = 255;
private Canvas drawCanvas;
private Bitmap canvasBitmap;
private static final class Vector2 {
public Vector2(float x, float y) {
this.x = x;
this.y = y;
}
public final float x;
public final float y;
}
public DrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
ctx = context;
setupDrawing();
}
private void setupDrawing() {
brushSize = getResources().getInteger(R.integer.small_size);
lastBrushSize = brushSize;
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setDither(true);
drawPaint.setStrokeWidth(brushSize);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}
private void touch_start(float x, float y) {
undonePaths.clear();
drawPath.reset();
drawPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
drawPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
customBrushMap.put(drawPath, mPositions);
}
private void touch_up() {
drawPath.lineTo(mX, mY);
drawCanvas.drawPath(drawPath, drawPaint);
paths.add(drawPath);
brushMap.put(drawPath, brushSize);
drawPath = new Path();
drawPath.reset();
invalidate();
}
public void onClickUndo() {
if (paths.size() > 0) {
undonePaths.add(paths.remove(paths.size() - 1));
invalidate();
}
}
public void onClickRedo() {
if (undonePaths.size() > 0) {
paths.add(undonePaths.remove(undonePaths.size() - 1));
invalidate();
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
//detect user touch
float x = event.getX();
float y = event.getY();
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
if (isCustomBrush) {
mPositions.add(new Vector2(x - mBitmapBrushDimensions.x / 2, y - mBitmapBrushDimensions.y / 2));
}
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_POINTER_DOWN:
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
for (Path p : paths) {
drawPaint.setColor(colorsMap.get(p));
drawPaint.setShader(shaderMap.get(p));
drawPaint.setStrokeWidth(brushMap.get(p));
drawPaint.setAlpha(opacityMap.get(p));
if (isCustomBrush) {
if (customBrushMap.get(p) != null) {
for (Vector2 pos : customBrushMap.get(p)) {
Paint paint = new Paint();
ColorFilter filter = new PorterDuffColorFilter(selectedColor, PorterDuff.Mode.SRC_IN);
paint.setColorFilter(filter);
canvas.drawBitmap(mBitmapBrush, pos.x, pos.y, paint);
}
}
} else {
canvas.drawPath(p, drawPaint);
drawPaint.setColor(selectedColor);
drawPaint.setStrokeWidth(brushSize);
canvas.drawPath(drawPath, drawPaint);
}
}
canvas.restore();
}
public void setCustomBrush(Activity activity, String customBrush) {
isCustomBrush = true;
invalidate();
int patternID = getResources().getIdentifier(customBrush, "drawable", "com.androidapp.drawingstutorial");
mBitmapBrush = BitmapFactory.decodeResource(getResources(), patternID);
mBitmapBrushDimensions = new Vector2(mBitmapBrush.getWidth(), mBitmapBrush.getHeight());
}
}
To make your brush look "smoother" not 100% sure what is meant by that, but you would need to tighten up the dots on the image you are using for your custom brush. Making them more compact will make the edges look smoother.
As for undo redo, in your touchUp method your never adding to customBrushMap only brushMap. So in the ondraw there is nothing to draw since that map will always be empty.
Related
I have created an Android 5.1 application with which I can write with a stylus on a tablet using SurfaceView and Canvas. You can find my code below. Unfortunately, the writing is very slow, especially when lot of text is written it starts getting slow. I think the problem is that it is rendered in software. Of course, a possibility to speedup would be to use OpenGL but I don't know it and I think this has a too steep learning curve for me right now.
Nevertheless, is there a possibility to speedup my code (i.e. to make it more responsible when writing)? Or else is it easy to change my code to OpenGL?
public class DrawingView extends SurfaceView implements OnTouchListener {
private Bitmap mBitmap;
private Canvas m_Canvas;
private Path m_Path;
private Paint m_Paint;
private ArrayList<Pair<Path, Paint>> paths = new ArrayList<>();
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private boolean isEraserActive = false;
private int pathCount = 0;
private View rectangleView;
public DrawingView(Context context, AttributeSet attr) {
super(context, attr);
setFocusable(true);
setFocusableInTouchMode(true);
setBackgroundColor(Color.TRANSPARENT);
this.setOnTouchListener(this);
onCanvasInitialization();
setAlpha(0.99f);
}
public void onCanvasInitialization() {
m_Paint = new Paint();
m_Paint.setAntiAlias(true);
m_Paint.setDither(true);
m_Paint.setColor(Color.parseColor("#000000"));
m_Paint.setStyle(Paint.Style.STROKE);
m_Paint.setStrokeJoin(Paint.Join.ROUND);
m_Paint.setStrokeCap(Paint.Cap.ROUND);
m_Paint.setStrokeWidth(2);
m_Path = new Path();
Paint newPaint = new Paint(m_Paint);
paths.add(new Pair<>(m_Path, newPaint));
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444);
m_Canvas = new Canvas(mBitmap);
}
#Override
public boolean onTouch(View arg0, MotionEvent event) {
float x = event.getX();
float y = event.getY();
if (rectangleView != null) {
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) rectangleView.getLayoutParams();
params.leftMargin = (int) x;
params.topMargin = (int) y;
rectangleView.setLayoutParams(params);
return true;
} else {
if (event.getToolType(0) == MotionEvent.TOOL_TYPE_ERASER) {
isEraserActive = true;
} else {
isEraserActive = false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
}
#Override
protected void onDraw(Canvas canvas) {
for (Pair<Path, Paint> p : paths) {
canvas.drawPath(p.first, p.second);
}
}
private void touch_start(float x, float y) {
if (isEraserActive) {
m_Paint.setStrokeWidth(20);
m_Paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
Paint newPaint = new Paint(m_Paint);
paths.add(new Pair<>(m_Path, newPaint));
} else {
m_Paint.setColor(Color.BLACK);
m_Paint.setStrokeWidth(2);
m_Paint.setXfermode(null);
Paint newPaint = new Paint(m_Paint);
paths.add(new Pair<>(m_Path, newPaint));
pathCount++;
}
m_Path.reset();
m_Path.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
m_Path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touch_up() {
m_Path.lineTo(mX, mY);
m_Canvas.drawPath(m_Path, m_Paint);
m_Path = new Path();
Paint newPaint = new Paint(m_Paint);
paths.add(new Pair<>(m_Path, newPaint));
}
public void reset() {
for (Pair<Path, Paint> p : paths) {
p.first.reset();
}
paths.clear();
pathCount = 0;
mBitmap.eraseColor(Color.TRANSPARENT);
invalidate();
}
}
I have written an Android application with a Canvas for drawing with a stylus. It works well. When I'm pushing the upper function key of my stylus I would like to erase the drawing by brushing over the text. The normal drawing is in black, so I thought to do the erase with white (on top of the black line). My problem is that all lines change the color when I press the upper function key of the stylus (i.e. all lines are then white) instead of just painting the new draw line in white.
Another option would be to delete elements from the path for the erase. If somebody has a solution for this, I would be happy too.
The layout looks as follows:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
tools:context="StylusBaselineA">
<inf.ethz.ch.affectivestudy.CanvasView
android:id="#+id/baselineACanvas"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textColor="#FFFFFF" />
</android.support.constraint.ConstraintLayout>
The CanvasView class look as follows:
public class CanvasView extends View {
public int width;
public int height;
private Bitmap mBitmap;
private Canvas mCanvas;
private Path mPath;
private Path mPathErase;
Context context;
private Paint mPaint;
private Paint mPaintErase;
private float mX, mY;
private static final float TOLERANCE = 5;
private boolean erase = false;
public CanvasView(Context c, AttributeSet attrs) {
super(c, attrs);
context = c;
// we set a new Path
mPath = new Path();
mPathErase = new Path();
// and we set a new Paint with the desired attributes
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeWidth(4f);
mPaintErase = new Paint();
mPaintErase.setAntiAlias(true);
mPaintErase.setColor(Color.WHITE);
mPaintErase.setStyle(Paint.Style.STROKE);
mPaintErase.setStrokeJoin(Paint.Join.ROUND);
mPaintErase.setStrokeWidth(4f);
}
// override onSizeChanged
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// your Canvas will draw onto the defined Bitmap
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
// override onDraw
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw the mPath with the mPaint on the canvas when onDraw
if (erase) {
canvas.drawPath(mPathErase, mPaintErase);
} else {
canvas.drawPath(mPath, mPaint);
}
}
// when ACTION_DOWN start touch according to the x,y values
private void startTouch(float x, float y) {
if (erase) {
mPathErase.moveTo(x, y);
} else {
mPath.moveTo(x, y);
}
mX = x;
mY = y;
}
// when ACTION_MOVE move touch according to the x,y values
private void moveTouch(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOLERANCE || dy >= TOLERANCE) {
if (erase) {
mPathErase.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
} else {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
}
mX = x;
mY = y;
}
}
public void clearCanvas() {
mPath.reset();
mPathErase.reset();
invalidate();
}
// when ACTION_UP stop touch
private void upTouch() {
if (erase) {
mPathErase.lineTo(mX, mY);
} else {
mPath.lineTo(mX, mY);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
if (event.getToolType(0) == MotionEvent.TOOL_TYPE_ERASER) {
// Upper function key of stylus
erase = true;
} else if (event.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER) {
// Touch input
erase = false;
return false;
} else {
erase = false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startTouch(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
moveTouch(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
upTouch();
invalidate();
break;
}
return true;
}
}
Try this.
public class CanvasActivity extends AppCompatActivity {
private Paint mPaint;
View mView;
Button clear;
private Canvas mCanvas;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_canvas);
RelativeLayout layout = (RelativeLayout) findViewById(R.id.Linear_layout);
mView = new DrawingView(this);
layout.addView(mView, new LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT));
clear= (Button) findViewById(R.id.clear);
clear.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mCanvas.drawColor(Color.WHITE);
}
});
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.GREEN);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(12);
}
public class DrawingView extends View {
public int width;
public int height;
private Bitmap mBitmap;
private Path mPath;
private Paint mBitmapPaint;
Context context;
private Paint circlePaint;
private Path circlePath;
public DrawingView(Context c) {
super(c);
context=c;
mPath = new Path();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
circlePaint = new Paint();
circlePath = new Path();
circlePaint.setAntiAlias(true);
circlePaint.setColor(Color.BLUE);
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setStrokeJoin(Paint.Join.MITER);
circlePaint.setStrokeWidth(4f);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap( mBitmap, 0, 0, mBitmapPaint);
canvas.drawPath( mPath, mPaint);
canvas.drawPath( circlePath, circlePaint);
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private void touch_start(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
mX = x;
mY = y;
circlePath.reset();
circlePath.addCircle(mX, mY, 30, Path.Direction.CW);
}
}
private void touch_up() {
mPath.lineTo(mX, mY);
circlePath.reset();
// commit the path to our offscreen
mCanvas.drawPath(mPath, mPaint);
// kill this so we don't double draw
mPath.reset();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
}
}
I have different set of colors in paint app. I am able to draw lines on canvas view with default color. But when i change the color it is changing color for all lines including old lines.
below is the code.
public class DrawingArea extends View {
private Path drawPath;
private Paint drawPaint, canvasPaint;
private int paintColor = 0xFF660000;
private Canvas drawCanvas;
private Bitmap canvasBitmap;
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Path> undonePaths = new ArrayList<Path>();
private ArrayList<Integer> colors = new ArrayList<Integer>();
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private static boolean mRedoStatus = false;
private static boolean mUndoStatus = false;
private float currentBrushSize;
private float lastBrushSize;
// for Undo, Redo
private int historyPointer = 0;
private boolean erase = false;
public DrawingArea(Context context) {
super(context);
setupDrawing();
}
public DrawingArea(Context context, AttributeSet attrs) {
super(context, attrs);
setupDrawing();
}
public void setupDrawing() {
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(20);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
//paths.add(drawPath);
}
public void setColor(String newColor) {
invalidate();
paintColor = Color.parseColor(newColor);
drawPaint.setColor(paintColor);
}
#Override
protected void onDraw(Canvas canvas) {
for (Path p : paths) {
canvas.drawPath(p, drawPaint);
}
canvas.drawPath(drawPath, drawPaint);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
private void touch_start(float x, float y) {
undonePaths.clear();
drawPath.reset();
drawPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
drawPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
public void setErase() {
canvasBitmap.eraseColor(Color.TRANSPARENT);
drawPath.reset();
invalidate();
}
private void touch_up() {
drawPath.lineTo(mX, mY);
// commit the path to our offscreen
drawCanvas.drawPath(drawPath, drawPaint);
paths.add(drawPath);
// kill this so we don't double draw
drawPath = new Path();
}
public void startNew() {
drawCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
invalidate();
}
public void undo() {
if (paths.size() > 0) {
undonePaths.add(paths.remove(paths.size() - 1));
invalidate();
} else {
setEmptyStatus(false);
}
}
public void setEmptyStatus(boolean status) {
mRedoStatus = status;
mUndoStatus = status;
}
public void redo() {
if (undonePaths.size() > 0) {
paths.add(undonePaths.remove(undonePaths.size() - 1));
invalidate();
} else {
setEmptyStatus(false);
}
}
public void setBrushSize(float newSize) {
float pixelAmount = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
newSize, getResources().getDisplayMetrics());
currentBrushSize = pixelAmount;
canvasPaint.setStrokeWidth(newSize);
}
public void setLastBrushSize(float lastSize) {
lastBrushSize = lastSize;
}
public float getLastBrushSize() {
return lastBrushSize;
}
}
It looks like you're setting the new color on your drawPaint variable. However, in your onDraw method:
for (Path p : paths) {
canvas.drawPath(p, drawPaint);
}
This loop is drawing all of the previous paths using the same Paint object, drawPaint, which is why they are all drawn with the same color. A possible solution to consider is saving each Paint object along with the Path, and call drawPath for each path and its corresponding Paint.Just as you have a list of all the old paths, you can also create a list of old paints.
private ArrayList<Paint> paints = new ArrayList<Paint>();
When you save each path to the paths list, you can add the current drawPaint to the list of paints. Then, in your onDraw method, you can change your loop to look something like this:
for(int i = 0 ; i < paths.size() ; i++) {
canvas.drawPath(paths.get(i), paints.get(i));
}
This would draw each path with its corresponding paint, preserving each paths color.
I am developing drawing module for my app. All features working fine. The problem is, everytime when I change the color, or set the brushSize or change to eraser, the first touch on the screen will draw with the previous settings. The second touch then will have the newest changes. Same goes to undo. Nothing change on first click. For second click and so on, it is working fine. This is my reference. The sample app in the reference also has the same problem. This is my code:
public class DrawingView extends View {
//drawing path
private Path drawPath;
private Point pointDraw;
//drawing and canvas paint
private Paint drawPaint, canvasPaint;
//initial color
private int paintColor;
//canvas
private Canvas drawCanvas;
//canvas bitmap
private Bitmap canvasBitmap;
//brush sizes
private float brushSize, lastBrushSize;
//private int defaultBrush = 10;
//erase flag
private boolean erase=false;
private int tempColor;
private boolean checkDraw = false;
private ArrayList<PathPoints> paths = new ArrayList<PathPoints>();
private ArrayList<PathPoints> undonePaths = new ArrayList<PathPoints>();
private ArrayList<Point> tempCanvas = new ArrayList<Point>();
private int countPoint = 0;
int x,y;
public DrawingView(Context context, AttributeSet attrs){
super(context, attrs);
setupDrawing();
}
//setup drawing
private void setupDrawing(){
//prepare for drawing and setup paint stroke properties
//brushSize = getResources().getInteger(R.integer.medium_size);
lastBrushSize = brushSize;
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(brushSize);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
paths.add(new PathPoints(drawPath, paintColor, brushSize, false));
drawCanvas = new Canvas();
}
//size assigned to view
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}
//draw the view - will be called after touch event
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
//canvas.drawPath(drawPath, drawPaint);
for (PathPoints p : paths) {
drawPaint.setColor(p.getColor());
drawPaint.setStrokeWidth(p.getStrokeSize());
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(p.getPath(), drawPaint);
}
setCheckDraw(true);
}
public int getArr()
{
return paths.size();
}
public boolean isCheckDraw() {
return checkDraw;
}
public void setCheckDraw(boolean checkDraw) {
this.checkDraw = checkDraw;
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 0;
private void touch_start(float x, float y) {
drawPath.reset();
drawPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
drawPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touch_up() {
drawPath.lineTo(mX, mY);
// commit the path to our offscreen
drawCanvas.drawPath(drawPath, drawPaint);
// kill this so we don't double draw
drawPath = new Path();
paths.add(new PathPoints(drawPath, paintColor, brushSize, false));
}
//register user touches as drawing action
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
//respond to down, move and up events
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(touchX, touchY);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
touch_up();
//invalidate();
break;
}
//redraw
invalidate();//do not call frequently
return true;
}
public void validateDraw()
{
invalidate();
}
//update color
public void setColor(String newColor){
//invalidate();
paintColor = Color.parseColor(newColor);
drawPaint.setColor(paintColor);
}
public void setColor(int newColor){
//invalidate();
paintColor = newColor;
drawPaint.setColor(paintColor);
tempColor = paintColor;
}
//set brush size
public void setBrushSize(float newSize){
float pixelAmount = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
newSize, getResources().getDisplayMetrics());
brushSize=pixelAmount;
}
//get and set last brush size
public void setLastBrushSize(float lastSize){
lastBrushSize=lastSize;
}
public float getLastBrushSize(){
drawPath.reset();
return lastBrushSize;
}
//set erase true or false
public void setErase(boolean isErase)
{
//drawPath.reset();
erase=isErase;
if(erase)
this.setColor("#FFFFFFFF");
else
//drawPaint.setXfermode(null);
this.setColor(tempColor);
}
//// new drawing
public void startNew(){
//drawCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
paths.clear();
undonePaths.clear();
invalidate();
}
public void onClickUndo() {
if (paths.size() > 0) {
undonePaths.add(paths.remove(paths.size() - 1));
invalidate();
} else {
}
// toast the user
}
public void onClickRedo() {
if (undonePaths.size() > 0) {
paths.add(undonePaths.remove(undonePaths.size() - 1));
invalidate();
} else {
}
// toast the user
}
Thank in advance
Welcome guys,
I'm creating my drawing app using this code:
public class DrawingPanel extends View implements OnTouchListener {
private Canvas mCanvas;
private Path mPath;
private Paint mPaint, circlePaint, outercirclePaint;
// private ArrayList<Path> undonePaths = new ArrayList<Path>();
private float xleft, xright, xtop, xbottom;
public DrawingPanel(Context context) {
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
init();
}
public void delete() {
paths.remove(paths.size() - 1);
invalidate();
}
public void init() {
circlePaint = new Paint();
mPaint = new Paint();
outercirclePaint = new Paint();
outercirclePaint.setAntiAlias(true);
circlePaint.setAntiAlias(true);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLACK);
outercirclePaint.setColor(0x44FFFFFF);
circlePaint.setColor(0xAADD5522);
outercirclePaint.setStyle(Paint.Style.STROKE);
circlePaint.setStyle(Paint.Style.FILL);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(6);
outercirclePaint.setStrokeWidth(6);
mCanvas = new Canvas();
mPath = new Path();
paths.add(mPath);
}
public void colorChanged(int color) {
mPaint.setColor(color);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void onDraw(Canvas canvas) {
for (Path p : paths) {
canvas.drawPath(p, mPaint);
}
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 0;
private void touch_start(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touch_up() {
mPath.lineTo(mX, mY);
// commit the path to our offscreen
mCanvas.drawPath(mPath, mPaint);
RectF rectF = new RectF();
mPath.computeBounds(rectF, true);
if(!rectF.isEmpty()){
Log.d("Here", "Empty");
}
regions.add(rectF);
// kill this so we don't double draw
mPath = new Path();
paths.add(mPath);
}
#Override
public boolean onTouch(View arg0, MotionEvent event) {
float x = event.getX();
float y = event.getY();
invalidate();
if (!isErasing) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
} else {
for (int i = 0; i < regions.size(); i++) {
if (regions.get(i).contains((int) x, (int) y)) {
Log.d("Here", "Here");
}
}
}
return true;
}
}
Now I can really undo and redo
I wanted to be able to delete and touch path (Yes the whole path like Samsung write on screen app)
For this reason I tried as shown above the RectF and contains but from what I can tell the RectF selects rectangle which contains the path (it doesn't contain the path's region only but if it's curved the RectF will contain the path and the area around it), I tried regions too.
I couldn't get the only touched path in erasing mode to remove it
So any suggestions will be appreciated :)