Hi i am erasing bitmap that is draw at canvas with touch (fingers) that is working fine the problem i am facing is after rotate bitmap at canvas paths draw in opposition direction mean bitmap erase in opposition direction of my finger touch .
DrawingPane.class
public class DrawingPanel extends ImageView implements OnTouchListener {
private Matrix mMatrix = new Matrix();
private float mScaleFactor = 1f;
private float mRotationDegrees = 0.f;
private float mFocusX = 0.f;
private float mFocusY = 0.f;
private int mAlpha = 255;
private int mImageHeight, mImageWidth;
private ScaleGestureDetector mScaleDetector;
private RotateGestureDetector mRotateDetector;
private MoveGestureDetector mMoveDetector;
private ShoveGestureDetector mShoveDetector;
private boolean isMoving=false;
EditPhotoActivity editActivity;
Bitmap overlayDefault;
Bitmap overlay;
Bitmap bmp,bmp2;
Paint pTouch;
int whichTabSelected=0;
private Path mPath;
Display display ;
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Float> xlist = new ArrayList<Float>();
private ArrayList<Float> ylist = new ArrayList<Float>();
#SuppressLint("NewApi")
public DrawingPanel(Context context, int colorPaint,Bitmap bmp) {
super(context);
if (Build.VERSION.SDK_INT >= 11) {
setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
display = ((Activity)context).getWindowManager().getDefaultDisplay();
mFocusX = display.getWidth()/2f;
mFocusY = display.getHeight()/2f;
try {
overlayDefault=bmp;
overlay=bmp;
overlay=overlay.copy(Config.ARGB_8888, true);
overlay.setHasAlpha(true);
} catch (Exception e) {
e.printStackTrace();
}
mImageHeight = getHeight();
mImageWidth = getWidth();
// Setup Gesture Detectors
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
mRotateDetector = new RotateGestureDetector(context, new RotateListener());
mMoveDetector = new MoveGestureDetector(context, new MoveListener());
mShoveDetector = new ShoveGestureDetector(context, new ShoveListener());
pTouch = new Paint(Paint.ANTI_ALIAS_FLAG);
pTouch.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
pTouch.setColor(Color.TRANSPARENT);
//pTouch.setMaskFilter(new BlurMaskFilter(30, Blur.SOLID));
pTouch.setStyle(Paint.Style.STROKE);
pTouch.setStrokeJoin(Paint.Join.ROUND);
pTouch.setStrokeCap(Paint.Cap.ROUND);
pTouch.setStrokeWidth(50);
pTouch.setAntiAlias(true);
setFocusable(true);
setFocusableInTouchMode(true);
mPath = new Path();
paths.add(mPath);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mImageHeight=getHeight();
mImageWidth=getWidth();
bmp = Bitmap.createScaledBitmap(overlay, w, h, false);
bmp2 = Bitmap.createScaledBitmap(overlayDefault, w, h, false);
overlay = bmp.copy(Config.ARGB_8888, true);
overlayDefault = bmp2.copy(Config.ARGB_8888, true);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
#Override
protected void onDraw(Canvas canvas) {
// mCanvas.drawBitmap(overlayDefault,0, 0, null); //exclude this line to show all as you draw
// mCanvas.drawCircle(X, Y, 80, pTouch);
//draw the overlay over the background
float scaledImageCenterX = (mImageWidth*mScaleFactor)/2;
float scaledImageCenterY = (mImageHeight*mScaleFactor)/2;
mMatrix.reset();
mMatrix.postScale(mScaleFactor, mScaleFactor);
mMatrix.postRotate(mRotationDegrees, scaledImageCenterX, scaledImageCenterY);
if(isMoving)
{
mMatrix.postTranslate(mFocusX - scaledImageCenterX, mFocusY - scaledImageCenterY);
}
else
{
mMatrix.postTranslate(0,0);
}
canvas.setMatrix(mMatrix);
canvas.drawBitmap(overlay,0,0, null);
for (Path p : paths) {
canvas.drawPath(p, pTouch);
}
super.onDraw(canvas);
}
public Bitmap getBitmap(){
Bitmap b = Bitmap.createScaledBitmap(overlay,display.getWidth() ,display.getWidth(), false);
overlay = b.copy(Config.ARGB_8888, true);
return overlay;
}
public void setBitmap(Bitmap bmp1){
overlay = bmp1;
invalidate();
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 0;
public void touch_start(float x, float y) {
if(xlist.size()>0 && ylist.size()>0){
xlist.clear();
ylist.clear();
}
xlist.add(x);
ylist.add(y);
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
mPath.transform(mMatrix, mPath);
invalidate();
}
public 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;
mPath.transform(mMatrix, mPath);
}
xlist.add(x);
ylist.add(y);
invalidate();
}
public void touch_up() {
mPath.lineTo(mX, mY);
mPath = new Path();
mPath.transform(mMatrix, mPath);
paths.add(mPath);
invalidate();
}
public void OnTouchParent(MotionEvent event){
mScaleDetector.onTouchEvent(event);
mRotateDetector.onTouchEvent(event);
mMoveDetector.onTouchEvent(event);
mShoveDetector.onTouchEvent(event);
float scaledImageCenterX = (mImageWidth*mScaleFactor)/2;
float scaledImageCenterY = (mImageHeight*mScaleFactor)/2;
mMatrix.reset();
mMatrix.postScale(mScaleFactor, mScaleFactor);
mMatrix.postRotate(mRotationDegrees, scaledImageCenterX, scaledImageCenterY);
mMatrix.postTranslate(mFocusX - scaledImageCenterX, mFocusY - scaledImageCenterY);
float x = event.getX();
float y = event.getY();
/*switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if(whichTabSelected==Constant.ERASE)
{
touch_start(x, y);
invalidate();
}
break;
case MotionEvent.ACTION_MOVE:
if(whichTabSelected==Constant.ERASE)
{
touch_move(x, y);
invalidate();
}
break;
case MotionEvent.ACTION_UP:
if(whichTabSelected==Constant.ERASE)
{
touch_up();
invalidate();
}
break;
}
if(whichTabSelected==Constant.ERASE)
{
return true;
}
else
{
return false;
}*/
invalidate();
}
#Override
public boolean onTouch(View arg0, MotionEvent event) {
if(getTabMode()==Constant.PANZOOM)
{
mScaleDetector.onTouchEvent(event);
mRotateDetector.onTouchEvent(event);
mMoveDetector.onTouchEvent(event);
mShoveDetector.onTouchEvent(event);
float scaledImageCenterX = (mImageWidth*mScaleFactor)/2;
float scaledImageCenterY = (mImageHeight*mScaleFactor)/2;
}
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if(getTabMode()==Constant.ERASE)
{
touch_start(x, y);
invalidate();
}
break;
case MotionEvent.ACTION_MOVE:
if(getTabMode()==Constant.ERASE)
{
touch_move(x, y);
invalidate();
}
break;
case MotionEvent.ACTION_UP:
if(getTabMode()==Constant.ERASE)
{
touch_up();
invalidate();
}
break;
}
invalidate();
return true;
}
public void setBottomTabMode(int mode)
{
whichTabSelected=mode;
}
public int getTabMode()
{
return whichTabSelected;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor(); // scale change since previous event
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
return true;
}
}
private class RotateListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
#Override
public boolean onRotate(RotateGestureDetector detector) {
mRotationDegrees -= detector.getRotationDegreesDelta();
return true;
}
}
private class MoveListener extends MoveGestureDetector.SimpleOnMoveGestureListener {
#Override
public boolean onMove(MoveGestureDetector detector) {
PointF d = detector.getFocusDelta();
mFocusX += d.x;
mFocusY += d.y;
isMoving=true;
// mFocusX = detector.getFocusX();
// mFocusY = detector.getFocusY();
return true;
}
}
private class ShoveListener extends ShoveGestureDetector.SimpleOnShoveGestureListener {
#Override
public boolean onShove(ShoveGestureDetector detector) {
mAlpha += detector.getShovePixelsDelta();
if (mAlpha > 255)
mAlpha = 255;
else if (mAlpha < 0)
mAlpha = 0;
return true;
}
}
}
I fixed my problem. Actually when i rotate canvas the event.getX() and event.getY() were not map to current rotation of matrix so by adding this line in mMatrix.invert(tempMatrix); in OnDraw() and also map current x,y in OnTouch() by adding this in OnTouch() method .
float[] coords = new float[] { event.getX(), event.getY() };
tempMatrix.mapPoints(coords);
float x = coords[0];//event.getX();
float y = coords[1];//event.getY();
its working fine .
This effect is happening because you are applying the matrix twice to paths.
Once at touch_start/touch_move by doing mPath.transform(mMatrix, mPath);.
And then again at onDraw(Canvas canvas) by canvas.setMatrix(mMatrix); and then canvas.drawPath(p, pTouch);.
To fix, try to remove the mPath.transform(mMatrix, mPath); from touch_start/touch_move.
Also, I do not know if it is a good practice to set the matrix directly to the canvas. Instead of canvas.setMatrix(mMatrix);, I would prefer to do the following:
canvas.save();
canvas.concat(mMatrix);
//write the code....
canvas.restore();
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 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.
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