Search code examples
androidcanvaspainteraseporter-duff

Android Eraser in paint canvas does not erase


I have the following code to activate/deactivate the eraser:

public PorterDuffXfermode clear = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);    
eraseB.setOnClickListener(new OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        if (!eraser) {
                            eraser = true;
                            eraseB.setImageResource(R.drawable.erase_on);
                            paint = new Paint(Paint.DITHER_FLAG);
                            paint.setColor(0x00000000);
                            paint.setAlpha(0x00);
                            paint.setXfermode(clear);
                            paint.setStyle(Paint.Style.STROKE);
                            paint.setStrokeJoin(Paint.Join.ROUND);
                            paint.setStrokeCap(Paint.Cap.ROUND);
                            paint.setStrokeWidth(stroke);
                            paintv.setPaint(paint);
                        } else {
                            eraser = false;
                            eraseB.setImageResource(R.drawable.erase);
                            paint = new Paint(Paint.DITHER_FLAG);
                            paint.setDither(true);
                            paint.setXfermode(null);
                            paint.setColor(Color.RED);
                            paint.setStyle(Paint.Style.STROKE);
                            paint.setStrokeJoin(Paint.Join.ROUND);
                            paint.setStrokeCap(Paint.Cap.ROUND);
                            paint.setStrokeWidth(stroke);
                            paintv.setPaint(paint);
                        }
                    }
                });

setPaint is from my customView:

public void setPaint(Paint paint) {
    this.paint = paint;
    LogService.log("in setPaint", "paint = " + paint);
}

and onDraw I use:

canvas.drawPath(mPath, paint);

If I deactivate the eraser, it will draw with a red line, but instead, if I activate the eraser, instead of erasing, it will draw a black line. How can I fix this


Solution

  • Canvas does not support eraser while Bitmap does.

    Basic workaround flow:

    1. Create another canvas

    2. Create a bitmap

    3. Set that bitmap to that canvas

      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);
      }
      
    4. Record the touches on that bitmap, including paint and eraser

      public boolean onTouchEvent(MotionEvent event) {
          float eventX = event.getX();
          float eventY = event.getY();
      
          switch (event.getAction()) {
          case MotionEvent.ACTION_DOWN:
              currentStroke = new Stroke();
              currentStroke.color = paint;
              currentStroke.path.moveTo(eventX, eventY);
              currentStroke.path.lineTo(eventX, eventY);
      
              synchronized (strokes) {
                  strokes.add(currentStroke);
              }
              lastTouchX = eventX;
              lastTouchY = eventY;
              // There is no end point yet, so don't waste cycles invalidating.
              return true;
      
          case MotionEvent.ACTION_MOVE:
          case MotionEvent.ACTION_UP:
              // Start tracking the dirty region.
              resetDirtyRect(eventX, eventY);
      
              // When the hardware tracks events faster than they are delivered,
              // the
              // event will contain a history of those skipped points.
              int historySize = event.getHistorySize();
              for (int i = 0; i < historySize; i++) {
                  float historicalX = event.getHistoricalX(i);
                  float historicalY = event.getHistoricalY(i);
              expandDirtyRect(historicalX, historicalY);
                  if (i == 0) {
                      lastX = historicalX;
                      lastY = historicalY;
                      currentStroke.path.lineTo(historicalX, historicalY);
                  } else {
                      currentStroke.path.quadTo(lastX, lastY,
                          (historicalX + lastX) / 2,
                          (historicalY + lastY) / 2);
                  }
              }
      
          // After replaying history, connect the line to the touch point.
              if(historySize==0){
                  long duration=event.getEventTime()-event.getDownTime();
                  float offset=0.1f;
                  if(duration<300){
                      offset=50.0f/duration;
                  }
                  currentStroke.path.lineTo(eventX+offset, eventY+offset);
              }else{
                  currentStroke.path.lineTo(eventX, eventY);
              }
              synchronized (strokes) {
                  strokes.add(currentStroke);
              }
      
              break;
      
          default:
          return false;
          }
      
      // Include half the stroke width to avoid clipping.
          float width = paint.getStrokeWidth() / 2;
          invalidate((int) (dirtyRect.left - width),
              (int) (dirtyRect.top - width), (int) (dirtyRect.right + width),
              (int) (dirtyRect.bottom + width));
      
          lastTouchX = eventX;
          lastTouchY = eventY;
      
          return true;
          }
      
    5. Draw that bitmap on the canvas of your View

      protected void onDraw(Canvas canvas) {
          synchronized (strokes) {
              if (strokes.size() > 0) {
                  for (Stroke s : strokes) {
                      cacheCanvas.drawPath(s.path, s.color);
                  }
                  canvas.drawBitmap(foreground, 0, 0, null);
                  strokes.clear();
              }
          }
      }