Как стереть / вырезать из Canvas CustomPaint?

Я уже пробовал использовать Canvas.clipPath вместе с GestureDetector быть похожим на ластик на холсте, где я использую CustomPaint внутри Container с imageDecoration установлен, поэтому я подумал, может быть, есть другое решение, используя Canvas.drawPath вдоль установки

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

но он рисует черные линии вместо того, чтобы стирать

есть идеи, как это обойти?

Спасибо

5 ответов

Ключ - позвонить 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();
}

Результат образца:

Пусть этот код вам поможет!

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

Возможно, вам нужно добавить этот код в виджет Statefull.

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,99:

              Opacity(
          opacity: .99,
          child: CustomPaint(
            size: const Size(double.infinity, 100),
            painter: MyPainter(),
          ),
        ),

Я не знаю почему, но это решает проблему без каких-либо изменений в вашем классе художника.

Может этот код вам помочь...

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});
    }

Пришел сюда в поисках противоположного (обрезать углы квадрата и сохранить внутреннюю часть). Цель состояла в том, чтобы нарисовать прямоугольник со скругленными углами из нескольких перекрывающихся прямоугольников без скруглений.

Вот что сработало для этого:

      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();
Другие вопросы по тегам