如何从Canvas CustomPaint中擦除/裁剪?

11

我已经尝试使用Canvas.clipPathGestureDetector,使其像画布上的橡皮擦一样。在Container内使用CustomPaint与设置了imageDecorationCanvas.drawPath或许可以找到另一个解决方法。

 final Paint _eraserPaint = Paint()
    ..color = Colors.transparent
    ..blendMode = BlendMode.clear
    ..strokeWidth = 8
    ..style = PaintingStyle.stroke
    ..isAntiAlias = true;

但它会画出黑线而不是擦除。

有什么办法可以解决这个问题吗?

谢谢。

5个回答

12
关键是在绘制任何可能需要擦除的内容之前调用saveLayer。完成这个步骤后(为您创建一个新图层供您使用),您可以使用任何Color填充或使用BlendMode.clear擦除进行绘制。最后,调用restore将新图层“合并”到其他现有图层中。
例如,让我们画一个红色正方形并从中减去一个圆:
void paint(Canvas canvas, Size size) {
  canvas.saveLayer(Rect.largest, Paint());
  canvas.drawRect(Rect.fromLTWH(0, 0, 80, 80), Paint()..color = Colors.red);
  canvas.drawCircle(Offset(40, 40), 40, Paint()..blendMode = BlendMode.clear);
  canvas.restore();
}

示例结果:

一个中间切有圆形的正方形


2

希望这段代码能对你有所帮助!

class DrawingPainter extends CustomPainter {

  List<DrawingPoints> pointsList;
  List<Offset> offsetPoints = List();
  
  DrawingPainter({
    this.pointsList,
  });

  @override
  void paint(Canvas canvas, Size size) {
    canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), Paint());
    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);
        canvas.drawCircle(pointsList[i].points, pointsList[i].paint.strokeWidth/2, pointsList[i].paint);
      }
    }
    canvas.restore();
  }

  @override
  bool shouldRepaint(DrawingPainter oldDelegate) => true;
}

class DrawingPoints {
  Paint paint;
  Offset points;
  DrawingPoints({this.points, this.paint});
}

您需要使用saveLayer函数,然后将其还原来保存Paint。

可能您需要将此代码添加到Stateful小部件中。

void changeBrush(bool isErease){
    setState(() {
      if ( isErease ){
        paint = Paint();
        paint.blendMode = BlendMode.clear;
        paint.color = Colors.white;
        paint.strokeWidth = strokeWidth;
      }else{
        paint = Paint();
        paint.isAntiAlias = true;
        paint.color = selectedColor.withOpacity(opacity);
        paint.strokeWidth = strokeWidth;
      }
    });
  }

0

来到这里是为了寻找相反的效果(剪裁正方形的角落并保留内部)。目标是从几个非圆角重叠的矩形中绘制出一个圆角矩形。

以下是实现该效果的方法:

canvas.save();

RRect clipRectangle = RRect.fromRectAndRadius(
  Rect.fromLTWH(0, 0, 80, 80),
  Radius.circular(4),
);
canvas.clipRRect(clipRectangle);

canvas.drawRect(Rect.fromLTWH(0, 0, 80, 80), Paint()..color = Colors.red);
canvas.drawRect(Rect.fromLTWH(0, 20, 80, 80), Paint()..color = Colors.blue);

canvas.restore();

0
将您的自定义绘制小部件包装到不透明度为0.99的不透明度小部件中:
        Opacity(
          opacity: .99,
          child: CustomPaint(
            size: const Size(double.infinity, 100),
            painter: MyPainter(),
          ),
        ),

我不知道为什么,但这样修复问题而不需要更改您的绘图类。

0

希望这段代码能对你有所帮助...

import 'dart:convert';
    import 'dart:io';
    import 'dart:typed_data';
    import 'dart:ui' as ui;
    
    import 'package:animated_floatactionbuttons/animated_floatactionbuttons.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';
    import 'package:flutter/services.dart';
    import 'package:image_gallery_saver/image_gallery_saver.dart';
    import 'package:image_picker/image_picker.dart';
    import 'package:permission_handler/permission_handler.dart';
    
    
    var appbarcolor = Colors.blue;
    class CanvasPainting_test extends StatefulWidget {
    
      @override
      _CanvasPainting_testState createState() => _CanvasPainting_testState();
    }
    
    class _CanvasPainting_testState extends State<CanvasPainting_test> {
      GlobalKey globalKey = GlobalKey();
    
      List<TouchPoints> points = List();
      double opacity = 1.0;
      StrokeCap strokeType = StrokeCap.round;
      double strokeWidth = 3.0;
      double strokeWidthforEraser = 3.0;
      Color selectedColor;
    
      Future<void> _pickStroke() async {
        //Shows AlertDialog
        return showDialog<void>(
          context: context,
    
          //Dismiss alert dialog when set true
          barrierDismissible: true, // user must tap button!
          builder: (BuildContext context) {
            //Clips its child in a oval shape
            return ClipOval(
              child: AlertDialog(
                //Creates three buttons to pick stroke value.
                actions: <Widget>[
                  //Resetting to default stroke value
                  FlatButton(
                    child: Icon(
                      Icons.clear,
                    ),
                    onPressed: () {
                      strokeWidth = 3.0;
                      Navigator.of(context).pop();
                    },
                  ),
                  FlatButton(
                    child: Icon(
                      Icons.brush,
                      size: 24,
                    ),
                    onPressed: () {
                      strokeWidth = 10.0;
                      Navigator.of(context).pop();
                    },
                  ),
                  FlatButton(
                    child: Icon(
                      Icons.brush,
                      size: 40,
                    ),
                    onPressed: () {
                      strokeWidth = 30.0;
                      Navigator.of(context).pop();
                    },
                  ),
                  FlatButton(
                    child: Icon(
                      Icons.brush,
                      size: 60,
                    ),
                    onPressed: () {
                      strokeWidth = 50.0;
                      Navigator.of(context).pop();
                    },
                  ),
                ],
              ),
            );
          },
        );
      }
    
      Future<void> _opacity() async {
        //Shows AlertDialog
        return showDialog<void>(
          context: context,
    
          //Dismiss alert dialog when set true
          barrierDismissible: true,
    
          builder: (BuildContext context) {
            //Clips its child in a oval shape
            return ClipOval(
              child: AlertDialog(
                //Creates three buttons to pick opacity value.
                actions: <Widget>[
                  FlatButton(
                    child: Icon(
                      Icons.opacity,
                      size: 24,
                    ),
                    onPressed: () {
                      //most transparent
                      opacity = 0.1;
                      Navigator.of(context).pop();
                    },
                  ),
                  FlatButton(
                    child: Icon(
                      Icons.opacity,
                      size: 40,
                    ),
                    onPressed: () {
                      opacity = 0.5;
                      Navigator.of(context).pop();
                    },
                  ),
                  FlatButton(
                    child: Icon(
                      Icons.opacity,
                      size: 60,
                    ),
                    onPressed: () {
                      //not transparent at all.
                      opacity = 1.0;
                      Navigator.of(context).pop();
                    },
                  ),
                ],
              ),
            );
          },
        );
      }
    
      Future<void> _pickStrokeforEraser() async {
        //Shows AlertDialog
        return showDialog<void>(
          context: context,
    
          //Dismiss alert dialog when set true
          barrierDismissible: true, // user must tap button!
          builder: (BuildContext context) {
            //Clips its child in a oval shape
            return ClipOval(
              child: AlertDialog(
                //Creates three buttons to pick stroke value.
                actions: <Widget>[
                  //Resetting to default stroke value
                  FlatButton(
                    child: Icon(
                      Icons.clear,
                    ),
                    onPressed: () {
                      strokeWidthforEraser = 3.0;
                      Navigator.of(context).pop();
                    },
                  ),
                  FlatButton(
                    child: Icon(
                      Icons.brush,
                      size: 24,
                    ),
                    onPressed: () {
                      strokeWidthforEraser = 10.0;
                      Navigator.of(context).pop();
                    },
                  ),
                  FlatButton(
                    child: Icon(
                      Icons.brush,
                      size: 40,
                    ),
                    onPressed: () {
                      strokeWidthforEraser = 30.0;
                      Navigator.of(context).pop();
                    },
                  ),
                  FlatButton(
                    child: Icon(
                      Icons.brush,
                      size: 60,
                    ),
                    onPressed: () {
                      strokeWidthforEraser = 50.0;
                      Navigator.of(context).pop();
                    },
                  ),
                ],
              ),
            );
          },
        );
      }
    
      Future<void> _save() async {
        RenderRepaintBoundary boundary =
        globalKey.currentContext.findRenderObject();
        ui.Image image = await boundary.toImage();
        ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
        Uint8List pngBytes = byteData.buffer.asUint8List();
    
        //Request permissions if not already granted
        if (!(await Permission.storage.status.isGranted))
          await Permission.storage.request();
    
        final result = await ImageGallerySaver.saveImage(
            Uint8List.fromList(pngBytes),
            quality: 60,
            name: "canvas_image");
        print(result);
      }
      String erase = 'yes';
      List<Widget> fabOption() {
        return <Widget>[
          FloatingActionButton(
            backgroundColor: appbarcolor,
            heroTag: "camera",
            child: Icon(Icons.camera),
            tooltip: 'camera',
            onPressed: () {
              //min: 0, max: 50
              setState(() {
                erase = 'yes';
                this._showDialog();
                // _save();
              });
            },
          ),
          FloatingActionButton(
            backgroundColor: appbarcolor,
            heroTag: "paint_save",
            child: Icon(Icons.file_download),
            tooltip: 'Save',
            onPressed: () {
              //min: 0, max: 50
              setState(() {
                erase = 'yes';
                _save();
              });
            },
          ),
          FloatingActionButton(
            backgroundColor: appbarcolor,
            heroTag: "paint_stroke",
            child: Icon(Icons.brush),
            tooltip: 'Stroke',
            onPressed: () {
              //min: 0, max: 50
              setState(() {
                erase = 'yes';
                _pickStroke();
              });
            },
          ),
          // FloatingActionButton(
          //   heroTag: "paint_opacity",
          //   child: Icon(Icons.opacity),
          //   tooltip: 'Opacity',
          //   onPressed: () {
          //     //min:0, max:1
          //     setState(() {
          //       _opacity();
          //     });
          //   },
          // ),
          FloatingActionButton(
            backgroundColor: appbarcolor,
            heroTag: "Erase",
            child: Icon(Icons.ac_unit),
            tooltip: 'Erase',
            onPressed: () {
              //min: 0, max: 50
              setState(() {
                // _save();
                // selectedColor = Colors.transparent;
                // print(Platform.isAndroid);
                erase = 'no';
                _pickStrokeforEraser();
              });
            },
          ),
          FloatingActionButton(
              backgroundColor: appbarcolor,
              heroTag: "Clear All",
              child: Icon(Icons.clear),
              tooltip: "Clear All",
              onPressed: () {
                setState(() {
                  erase = 'yes';
                  points.clear();
                });
              }),
          FloatingActionButton(
            backgroundColor: Colors.white,
            heroTag: "color_red",
            child: colorMenuItem(Colors.red),
            tooltip: 'Color',
            onPressed: () {
              setState(() {
                selectedColor = Colors.red;
              });
            },
          ),
          FloatingActionButton(
            backgroundColor: Colors.white,
            heroTag: "color_green",
            child: colorMenuItem(Colors.green),
            tooltip: 'Color',
            onPressed: () {
              setState(() {
                erase = 'yes';
                selectedColor = Colors.green;
              });
            },
          ),
          FloatingActionButton(
            backgroundColor: Colors.white,
            heroTag: "color_pink",
            child: colorMenuItem(Colors.pink),
            tooltip: 'Color',
            onPressed: () {
              setState(() {
                selectedColor = Colors.pink;
              });
            },
          ),
          FloatingActionButton(
            backgroundColor: Colors.white,
            heroTag: "color_blue",
            child: colorMenuItem(Colors.blue),
            tooltip: 'Color',
            onPressed: () {
              setState(() {
                erase = 'yes';
                selectedColor = Colors.blue;
              });
            },
          ),
        ];
      }
    
    
      void _showDialog() {
        // flutter defined function
        showDialog(
          context: context,
          builder: (BuildContext context) {
            // return object of type Dialog
            return AlertDialog(
    //          title: new Text("Alert Dialog title"),
              content: Row(
                // mainAxisSize: MainAxisSize.min,
                children: <Widget>[
                  RaisedButton(
                    onPressed: getImageCamera,
                    child: Text('From Camera'),
                  ),
                  SizedBox(
                    width: 5,
                  ),
                  RaisedButton(
                    onPressed: getImageGallery,
                    child: Text('From Gallery'),
                  )
                ],
              ),
            );
          },
        );
      }
      File _image;
      Future getImageCamera() async {
        var image = await ImagePicker.pickImage(source: ImageSource.camera);
    
        print(image);
    
        if (image != null) {
          setState(() {
            _image = image;
          });
          Navigator.of(context, rootNavigator: true).pop('dialog');
        }
      }
    
      Future getImageGallery() async {
        var image = await ImagePicker.pickImage(source: ImageSource.gallery);
    
        print(image);
    
        if (image != null) {
          setState(() {
            _image = image;
            print(_image);
          });
          Navigator.of(context, rootNavigator: true).pop('dialog');
        }
      }
      /*-------------------------------------*/
    
    
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('paint on image and erase'),backgroundColor: Colors.blueGrey
            // leading: IconButton(
            //   icon: Icon(Icons.arrow_back_ios),onPressed: (){
            //   Navigator.pop(context);
            // },),
          ),
          body: GestureDetector(
            onPanUpdate: (details) {
              setState(() {
                RenderBox renderBox = context.findRenderObject();
                erase!='no'?   points.add(TouchPoints(
                    points: renderBox.globalToLocal(details.globalPosition),
                    paint: Paint()
                      ..strokeCap = strokeType
                      ..isAntiAlias = true
                      ..color = selectedColor.withOpacity(opacity)
                      ..strokeWidth = strokeWidth))
    
                    : points.add(TouchPoints(
                    points:  renderBox.globalToLocal(details.globalPosition),
                    paint: Paint()
                      ..color = Colors.transparent
                      ..blendMode = BlendMode.clear
                      ..strokeWidth = strokeWidthforEraser
                      ..style = PaintingStyle.stroke
                      ..isAntiAlias = true
                ));
              });
            },
            onPanStart: (details) {
              setState(() {
                RenderBox renderBox = context.findRenderObject();
                erase!='no'?   points.add(TouchPoints(
                    points: renderBox.globalToLocal(details.globalPosition),
                    paint: Paint()
                      ..strokeCap = strokeType
                      ..isAntiAlias = true
                      ..color = selectedColor.withOpacity(opacity)
                      ..strokeWidth = strokeWidth))
    
                    : points.add(TouchPoints(
                    points:  renderBox.globalToLocal(details.globalPosition),
                    paint: Paint()
                      ..color = Colors.transparent
                      ..blendMode = BlendMode.clear
                      ..strokeWidth = strokeWidthforEraser
                      ..style = PaintingStyle.stroke
                      ..isAntiAlias = true
                ));
    
    
              });
            },
            onPanEnd: (details) {
              setState(() {
                points.add(null);
              });
            },
            child: RepaintBoundary(
              key: globalKey,
              child: Stack(
                children: <Widget>[
                  Center(
                    child: _image == null
                        ? Image.asset(
                      "assets/images/helo.jfif",
                    )
                        : Image.file(_image),
                  ),
                  CustomPaint(
                    size: Size.infinite,
                    painter: MyPainter(
                      pointsList: points,
                    ),
                  ),
                ],
              ),
            ),
          ),
          floatingActionButton: AnimatedFloatingActionButton(
              fabButtons: fabOption(),
              colorStartAnimation: appbarcolor,
              colorEndAnimation: Colors.red[300],
              animatedIconData: AnimatedIcons.menu_close),
        );
      }
    
      Widget colorMenuItem(Color color) {
        return GestureDetector(
          onTap: () {
            setState(() {
              selectedColor = color;
            });
          },
          child: ClipOval(
            child: Container(
              padding: const EdgeInsets.only(bottom: 8.0),
              height: 36,
              width: 36,
              color: color,
            ),
          ),
        );
      }
    
    
    
    
    
    
    
    
    }
    
    class MyPainter extends CustomPainter {
      MyPainter({this.pointsList});
    
      //Keep track of the points tapped on the screen
      List<TouchPoints> pointsList;
      List<Offset> offsetPoints = List();
    
      //This is where we can draw on canvas.
      @override
      void paint(Canvas canvas, Size size) {
        canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), Paint());
        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);
            canvas.drawCircle(pointsList[i].points, pointsList[i].paint.strokeWidth/2, pointsList[i].paint);
          }
        }
        canvas.restore();
      }
    
      //Called when CustomPainter is rebuilt.
      //Returning true because we want canvas to be rebuilt to reflect new changes.
      @override
      bool shouldRepaint(MyPainter oldDelegate) => true;
    }
    
    //Class to define a point touched at canvas
    class TouchPoints {
      Paint paint;
      Offset points;
      TouchPoints({this.points, this.paint});
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接