flutterdartcanvascustom-painter

Futter CustomPainer, How to erase without having to draw in white?


I am working on a small Flutter application for children, the concept is that there is an image in the background on which users have to draw. I am using Custom Painter for that.

There is a feature to erase what they draw and that is where the problem begins, the easy way to erase is to draw using the white color but when I use that it will also erase the background image which is very important. What is the proper way to erase without having to draw white on the image?

Here is the demo:

enter image description here

Here is the source code:

The main page

Listener(
      onPointerDown: (details) {
        RenderBox? renderBox = context.findRenderObject() as RenderBox?;
        if (renderBox != null) {
          setState(() {
            points.add(
                DrawingPoints(
                    points: renderBox.globalToLocal(details.position),
                    paint: Paint()
                      ..strokeCap = strokeCap
                      ..isAntiAlias = true
                      ..color = widget.selectedColor.withOpacity(1.0)
                      ..strokeWidth = strokeWidth
                )
            );
          });
        }
      },
      onPointerMove: (details) {
        RenderBox? renderBox = context.findRenderObject() as RenderBox?;
        if (renderBox != null) {
          setState(() {
            points.add(DrawingPoints(
                points: renderBox.globalToLocal(details.position),
                paint: Paint()
                  ..strokeCap = strokeCap
                  ..isAntiAlias = true
                  ..color = widget.selectedColor.withOpacity(1.0)
                  ..strokeWidth = strokeWidth));
          });
        }
      },
      onPointerUp: (details) {
        setState(() {
          points.add(null);
        });
      },
      child: CustomPaint(
        size: Size.infinite,
        painter: DrawingPainter(
          pointsList: points,
        ),
      ),
    )

DrawingPainter

class DrawingPainter extends CustomPainter {

  final List<DrawingPoints?> pointsList;
  List<Offset> offsetPoints = [];

  DrawingPainter({required this.pointsList});

  @override
  void paint(Canvas canvas, Size size) {
    for (int i = 0; i < pointsList.length - 1; i++) {

      if (pointsList[i] != null && pointsList[i + 1] != null) {
        canvas.drawLine(pointsList[i]!.points, pointsList[i +1]!.points, pointsList[i]!.paint,);
      }
      else if (pointsList[i] != null && pointsList[i + 1] == null) {
        offsetPoints.clear();
        offsetPoints.add(pointsList[i]!.points);
        offsetPoints.add(Offset(pointsList[i]!.points.dx + 0.1, pointsList[i]!.points.dy + 0.1));
        canvas.drawPoints(PointMode.points, offsetPoints, pointsList[i]!.paint);
      }
    }
  }
  @override
  bool shouldRepaint(DrawingPainter oldDelegate) => true;
}

DrawingPoints

class DrawingPoints {
  final Offset points;
  final Paint paint;

  const DrawingPoints({required this.points, required this.paint});

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;

    return other is DrawingPoints &&
      other.points.dx == points.dy &&
      other.paint.color == paint.color;
  }

  @override
  int get hashCode => points.hashCode ^ paint.hashCode;
}

Solution

  • I was finally been able to solve the issue.

    The solution looks very simple, what I did is to put the background image in a Stack as suggested by @iStornZ but I put the image above the custom painter, the image has to be transparent so that even when we paint in white the whole image will still be visible.

    Source code

    Stack(
        children: [
          CustomPaint(
            size: Size.infinite,
            painter: DrawingPainter(
              pointsList: points,
              erasingPoints: erasingPoints,
              myBackground: myBackground,
            ),
          ),
          Container(
            decoration: const BoxDecoration(
              image: DecorationImage(
                image: AssetImage('assets/puppy-dot.png'),
                fit: BoxFit.fitHeight,
              ),
            ),
          ),
        ],
      )
    

    Demo

    enter image description here