Как стереть / вырезать из 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();