Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cutting a hole in a shape with flutter canvas

How do I "cut a hole" in a shape with flutter canvas? I have this rather complex set of shapes that is made to look like a real world object. This object has a hole in it shaped like a rounded rectangle.

I would really like to subtract a RRect from a shape, but I cannot find any information on how to do this. canvas.clipRRect(myRRect) just removes everything that is not covered by myRRect. I want the opposite of that. i.e. to make a myRRect shape hole in the current canvas shape or shapes.

like image 407
Mathias Nielsen Avatar asked Jul 07 '19 16:07

Mathias Nielsen


3 Answers

You can use Path.combine along with the difference operation to create the hole.

The custom painter :

class HolePainter extends CustomPainter {
  
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint();
    paint.color = Colors.blue;
    canvas.drawPath(
        Path.combine(
          PathOperation.difference,
          Path()..addRRect(RRect.fromLTRBR(100, 100, 300, 300, Radius.circular(10))),
          Path()
            ..addOval(Rect.fromCircle(center: Offset(200, 200), radius: 50))
            ..close(),
        ),
        paint,
    );
  }

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

Usage :

class EditAvatar extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text('Hole in rounded rectangle'),
      ),
      body: CustomPaint(
        painter: HolePainter(),
        child: Container(),
      ),

  }

}

Result :

enter image description here

Of course if you want the hole to be a rounded rect simply substract an RRect instead of a Circle.

like image 177
Yann39 Avatar answered Nov 02 '22 06:11

Yann39


A solution is to use PathFillType.evenOdd:

// circle with empty RRect inside
final Path path = Path();
path.fillType = PathFillType.evenOdd;
path.addOval(Rect.fromCircle(center: center, radius: radius));
path.addRRect(RRect.fromRectAndRadius(
    Rect.fromCircle(center: center, radius: radius / 2),
    Radius.circular(radius / 10)));
canvas.drawPath(path, paint);

enter image description here

like image 25
monsieurtanuki Avatar answered Nov 02 '22 05:11

monsieurtanuki


You can try with different BlendMode in Custom Painter, Below is one of the example which you can refer:

class MyPaint extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // below one is big circle and instead of this circle you can draw your shape here.
    canvas.drawCircle(Offset(200, 200), 100, Paint()
      ..color = Colors.orange[200]
      ..style = PaintingStyle.fill);

    // below the circle which you want to create a cropping part.
    RRect rRect = RRect.fromRectAndRadius(Rect.fromCenter(center: Offset(200, 200), width: 75, height: 75), Radius.circular(8));
    canvas.drawRRect(rRect, Paint()
      ..color = Colors.orange[200]
      ..style = PaintingStyle.fill
      ..blendMode = BlendMode.dstOut);

    canvas.save();
    canvas.restore();
  }

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

Here, I have used BlendMode.dstOut which will be used to show the destination sources, but only where the two sources do not overlap.

like image 22
Jigar Avatar answered Nov 02 '22 06:11

Jigar