skip to Main Content

I have a custom canvas that sits as stack on top of my other widgets.

The PainterPoint is as follows:

class PainterPoint {
  bool isEnd;
  Color color;
  Offset offset;
  double strokeSize;
  BlendMode blendMode;

  PainterPoint({
    required this.offset,
    required this.isEnd,
    this.strokeSize = 2,
    this.color = Colors.blue,
    this.blendMode = BlendMode.color,
  });
}

And finally the BrushPainter is as:

class BrushPainter extends CustomPainter {
  final List<PainterPoint> points;

  BrushPainter({required this.points});

  @override
  void paint(Canvas canvas, Size size) {
    var paint = Paint()..strokeCap = StrokeCap.round;

    for (int i = 0; i < points.length - 1; i++) {
      if (points[i].isEnd) {
        continue;
      }
      final currentPoint = points[i];
      final nextPoint = points[i + 1];
      paint.color = currentPoint.color;
      paint.strokeWidth = currentPoint.strokeSize;
      paint.blendMode = currentPoint.blendMode;
      if (nextPoint.isEnd) {
        canvas.drawPoints(PointMode.points, [currentPoint.offset], paint);
      } else {
        canvas.drawLine(currentPoint.offset, nextPoint.offset, paint);
      }
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

But when I try to draw something the experience get laggy way too quickly. Even after a few points(after about 80-90 points) drawn the entire thing lags.

As you can see inside the gesture detector, if the EditorMode is EDIT_MODE and the isEraserPainter is true I am trying to cleanup the previously drawn points with BlendMode. So a lot of points will be drawn.

I have seen articles and posts on stack overflow but none of them included any sample code or good explanation as of yet.

2

Answers


  1. Chosen as BEST ANSWER

    The final solution following @HannesH's answer is:

    class BrushPainter extends CustomPainter {
      final List<PainterPoint> points;
    
      BrushPainter({required this.points});
    
      @override
      void paint(Canvas canvas, Size size) {
        var paint = Paint()
          ..strokeCap = StrokeCap.round
          ..style = PaintingStyle.stroke;
        Path path = Path();
        bool isNewLine = true;
    
        for (int i = 0; i < points.length; i++) {
          final currentPoint = points[i];
    
          if (currentPoint.isEnd) {
            if (!isNewLine) {
              canvas.drawPath(path, paint);
              path.reset();
              isNewLine = true;
            }
          } else {
            if (isNewLine) {
              path.moveTo(currentPoint.offset.dx, currentPoint.offset.dy);
              isNewLine = false;
            } else {
              path.lineTo(currentPoint.offset.dx, currentPoint.offset.dy);
            }
    
            paint.color = currentPoint.color;
            paint.strokeWidth = currentPoint.strokeSize;
            paint.blendMode = currentPoint.blendMode;
          }
        }
    
        if (!isNewLine) {
          canvas.drawPath(path, paint);
        }
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) {
        return true;
      }
    }
    

  2. Maybe instead of using canvas.drawLine for every segment, create a new path (before the loop) and add each segment with path.lineTo. After the loop draw the whole path by using canvas.drawPath.

    This will result in only one (complex but highly optimized) draw-call per frame instead of n.

    This has the additional benefit that you can simply swap lineTo with something more complex like quadraticBezierTo to smooth out the path.


    you would have to group your points into paths with corresponding blend mode though..

    also my inner programmer cries a little when i see ? true : false 😉

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search