Trying to create an eraser for a canvas

I am creating a drawing application that uses the DrawingSurfaceView class below. In this class, I have a Paint Labed eraserPaint that the user can turn on and off. When on this paint it is assumed that it will be an eraser, which is on its way. but instead, he simply draws a black line.

When I save the canvas as transparent png, the eraser is correct, but it appears black on the screen.

EraserPaint phone screenshot used to record "Erik" on blob

enter image description here

Saved PNG from canvas enter image description here

eraserPaint is as follows:

eraserPaint = new Paint(); eraserPaint.setAlpha(0); eraserPaint.setColor(Color.TRANSPARENT); eraserPaint.setStrokeWidth(60); eraserPaint.setStyle(Style.STROKE); eraserPaint.setMaskFilter(null); eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); eraserPaint.setAntiAlias(true); 

class WHOLE

  public KNDrawingSurfaceView(Context c, float width, float height, KNSketchBookActivity parent) { super(c); myWidth = width; myHeight = height; mBitmap = Bitmap.createBitmap((int) myWidth, (int) myHeight, Bitmap.Config.ARGB_8888); mCanvas = new Canvas(mBitmap); _parent = parent; mEmboss = new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f); tile = new Paint(); tileImage = BitmapFactory.decodeResource(getResources(), R.drawable.checkerpattern); shader = new BitmapShader(tileImage, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); tile.setShader(shader); mPath = new Path(); eraserPaint = new Paint(); eraserPaint.setAlpha(0x00); eraserPaint.setColor(Color.TRANSPARENT); eraserPaint.setStrokeWidth(60); eraserPaint.setStyle(Style.STROKE); //eraserPaint.setMaskFilter(null); eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); eraserPaint.setAntiAlias(true); mBitmapPaint = new Paint(Paint.DITHER_FLAG); mCanvas.drawRect(0, 0, myWidth, myHeight, tile); mCanvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); } @Override protected void onDraw(Canvas canvas) { if (!_parent.isDrawerOpen()&&mPaint!=null) { Log.v("onDraw:", "curent paths size:" + paths.size()); //mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); //canvas.drawPath(mPath, mPaint); for (int i=0;i< paths.size();i++) { tempPaint = paints.get(i); eraserPaint.setStrokeWidth(tempPaint.getStrokeWidth()); if(fills.get(i)){ tempPaint.setStyle(Style.FILL_AND_STROKE); eraserPaint.setStyle(Style.FILL_AND_STROKE); }else{ tempPaint.setStyle(Style.STROKE); eraserPaint.setStyle(Style.STROKE); } if(erasers.get(i)){ //tempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); canvas.drawPath(paths.get(i), eraserPaint); }else{ //tempPaint.setXfermode(null); canvas.drawPath(paths.get(i), tempPaint); } //canvas.drawPath(paths.get(i), tempPaint); } if(_parent.toggleFill.isChecked()){ mPaint.setStyle(Style.FILL_AND_STROKE); eraserPaint.setStyle(Style.FILL_AND_STROKE); }else{ mPaint.setStyle(Style.STROKE); eraserPaint.setStyle(Style.STROKE); } if(_parent.toggleErase.isChecked()){ //mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); canvas.drawPath(mPath, eraserPaint); }else{ //mPaint.setXfermode(null); canvas.drawPath(mPath, mPaint); } //canvas.drawPath(mPath, mPaint); } } public void onClickUndo() { if (paths.size() > 0) { undonePaths.add(paths.remove(paths.size() - 1)); undonePaints.add(paints.remove(paints.size() - 1)); undoneFills.add(fills.remove(fills.size() - 1)); undoneErasers.add(erasers.remove(erasers.size() - 1)); clearCanvasCache(); invalidate(); } else { } _parent.checkButtonStates(); } public void onClickRedo() { if (undonePaths.size() > 0) { paths.add(undonePaths.remove(undonePaths.size() - 1)); paints.add(undonePaints.remove(undonePaints.size() - 1)); fills.add(undoneFills.remove(undoneFills.size() - 1)); erasers.add(undoneErasers.remove(undoneErasers.size() - 1)); clearCanvasCache(); invalidate(); } else { } _parent.checkButtonStates(); } public void onClickClear() { paths.clear(); paints.clear(); fills.clear(); erasers.clear(); undoneFills.clear(); undonePaths.clear(); undonePaints.clear(); undoneErasers.clear(); clearCanvasCache(); invalidate(); _parent.checkButtonStates(); } public void saveDrawing() { FileOutputStream outStream = null; String fileName = "tempTag"; try { outStream = new FileOutputStream("/sdcard/" + fileName + ".png"); mBitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream); outStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { } } private float mX, mY; private static final float TOUCH_TOLERANCE = 4; private void touch_start(float x, float y) { undonePaths.clear(); undonePaints.clear(); undoneFills.clear(); 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 if(_parent.toggleErase.isChecked()){ mCanvas.drawPath(mPath, eraserPaint); erasers.add(true); paints.add(eraserPaint); }else{ mCanvas.drawPath(mPath, mPaint); erasers.add(false); paints.add(mPaint); } // kill this so we don't double draw paths.add(mPath); if(_parent.toggleFill.isChecked()){ fills.add(true); }else{ fills.add(false); } if(_parent.toggleErase.isChecked()){ erasers.add(true); }else{ erasers.add(false); } _parent.checkButtonStates(); mPath = new Path(); } @Override public boolean onTouchEvent(MotionEvent event) { if(mPaint==null &&!_parent._showingAlert){ _parent.showNoPaintAlert(); } if (!_parent.isDrawerOpen()&&mPaint!=null) { float x = event.getX(); float y = event.getY(); if (x > myWidth) { x = myWidth; } if (y > myHeight) { y = myHeight; } 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; } else { return true; } } public void clearCanvasCache() { mBitmap = Bitmap.createBitmap((int) myWidth, (int) myHeight, Bitmap.Config.ARGB_8888); mCanvas = new Canvas(mBitmap); } } 

I have to add that I am adding this custom view to the relative layout that has this checkered pattern as a background image.

PLEASE PLEASE PLEASE help .. I need the preview image NOT to appear black after using the eraser paint. I need him to show the checkered pattern at the back. I know that the eraser works as these black eraser tags keep as transparent.

NEW NOTE

I played and found something else interesting. onDraw experimenting, I tried to switch from painting to canvas , passed to the onDraw method, and directly to the canvas that I set in the contructor called mCanvas , and noticed that it wasn’t painting as far as I could see. I added the log to onDraw as follows:

  protected void onDraw(Canvas canvas) { Log.v("DRAWING SURFACE", "canvas:"+canvas+" mCanvas:"+mCanvas); 

which spits out

 06-21 11:10:43.994: V/DRAWING SURFACE(4532): canvas:android.view.Surface$CompatibleCanvas@42a8c030 mCanvas:android.graphics.Canvas@431df180 
+8
android android-canvas paint
source share
5 answers

I had the same problem with my application. I even tried the finger paint code example and still had the same problem. I could never use the eraser as a way, but I managed to find a workaround. Instead of drawing the path when erasing, I draw a circle (it can be any shape) when the user removes his finger or the "move" event occurs:

 case MotionEvent.ACTION_DOWN: mPaint.setStrokeWidth(25); mPaint.setXfermode(new PorterDuffXfermode( PorterDuff.Mode.CLEAR)); mCanvas.drawCircle(x, y, 10, mPaint); isErase = true; invalidate(); } touch_start(x, y); invalidate(); break; case MotionEvent.ACTION_MOVE: if(isErase) { mCanvas.drawCircle(x, y, 20, mPaint); } else{ touch_move(x, y); }invalidate(); break; 

It will take some time to include this in your code, but I guarantee that it will take less time than the amount of time that you have already spent trying to fix this problem. I can send you more of my PaintView if you think this will be useful.

+7
source share

The same problem arose, tried to find all other solutions, no luck.

But I have a workaround. You can add a bitmap to save strokes.

  public void init(int width, int height) { Log.i(TAG,"init with "+width+"x"+height); foreground = Bitmap.createBitmap(width, height, Config.ARGB_8888); cacheCanvas = new Canvas(); cacheCanvas.setBitmap(foreground); } 

Whenever there is any touch, write down the stroke with the current paint and current stroke width. (paint can be any color, including an eraser)

And then override the onDraw (Canvas) method. Since the bitmap supports the eraser until the canvas does this, we can first draw the resulting image in the bitmap and then draw the bitmap on the canvas.

  @Override protected void onDraw(Canvas canvas) { // Log.i(TAG,"onDraw called"); synchronized (strokes) { if (strokes.size() > 0) { for (Stroke s : strokes) { cacheCanvas.drawPath(s.path, s.color); } canvas.drawBitmap(foreground, 0, 0, null); strokes.clear(); } } } 

FYI, if the foreground bitmap is very large, performance will be poor. To solve this problem, we should cancel only those areas that have changed the last fingers.

+1
source share

This is just an assumption: it may be due to hardware acceleration. Try disabling hardware acceleration. If this helps, you can create a bitmap for the size of the view, draw all of your materials in that bitmap, and then draw a bitmap in the view canvas.

0
source share

To erase and invalidate the canvas, you must set the canvas setXfermode to null. check the last line of code.

 if(view.getId()==R.id.erase_btn) { erase_btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onDraw.setErase(true); } } } public void setErase(boolean isErase){ erase=isErase; if(erase) drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); else drawPaint.setXfermode(null); } 
0
source share

You can use a boolean when choosing an eraser (i.e. isEraser = true ), and in onDraw() you can draw a path if it is not an eraser.

 @Override protected void onDraw(Canvas canvas) { if(!isEraser ){ canvas.drawPath(mPath, mPaint); } } 
-2
source share

All Articles