首先,该程序的目标是允许用户使用手机或平板电脑签署官方文件。 该程序必须将图像保存为 png。
我使用 Flutter(和 dart)和 VS Code 开发这个应用。
什么有效:
-The user can draw on the canvas.
什么不起作用:
-the image can't be saved as a png
我发现了什么:
-The **Picture** get by ending the **PictureRecoder** of the canvas is empty (i tried to display it but no success)
-I tried to save it as a PNG using **PictureRecorder.EndRecording().toImage(100.0,100.0).toByteDate(EncodingFormat.png())** but the size is really small, and it can't be displayed.
如果你们中的一些人能给我一些关于问题可能出在哪里的提示,那就太好了。
仅供引用:Flutter 是开发 channel 的最新版本
完整代码如下:
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
///This class is use just to check if the Image returned by
///the PictureRecorder of the first Canvas is not empty.
///FYI : The image is not displayed.
class CanvasImageDraw extends CustomPainter {
ui.Picture picture;
CanvasImageDraw(this.picture);
@override
void paint(ui.Canvas canvas, ui.Size size) {
canvas.drawPicture(picture);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
///Class used to display the second canvas
class SecondCanvasView extends StatelessWidget {
ui.Picture picture;
var canvas;
var pictureRecorder= new ui.PictureRecorder();
SecondCanvasView(this.picture) {
canvas = new Canvas(pictureRecorder);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Image Debug'),
),
body: new Column(
children: <Widget>[
new Text('Top'),
CustomPaint(
painter: new CanvasImageDraw(picture),
),
new Text('Bottom'),
],
));
}
}
///This is the CustomPainter of the first Canvas which is used
///to draw to display the users draw/sign.
class SignaturePainter extends CustomPainter {
final List<Offset> points;
Canvas canvas;
ui.PictureRecorder pictureRecorder;
SignaturePainter(this.points, this.canvas, this.pictureRecorder);
void paint(canvas, Size size) {
Paint paint = new Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null)
canvas.drawLine(points[i], points[i + 1], paint);
}
}
bool shouldRepaint(SignaturePainter other) => other.points != points;
}
///Classes used to get the user draw/sign, send it to the first canvas,
///then display it.
///'points' list of position returned by the GestureDetector
class Signature extends StatefulWidget {
ui.PictureRecorder pictureRecorder = new ui.PictureRecorder();
Canvas canvas;
List<Offset> points = [];
Signature() {
canvas = new Canvas(pictureRecorder);
}
SignatureState createState() => new SignatureState(canvas, points);
}
class SignatureState extends State<Signature> {
List<Offset> points = <Offset>[];
Canvas canvas;
SignatureState(this.canvas, this.points);
Widget build(BuildContext context) {
return new Stack(
children: [
GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
RenderBox referenceBox = context.findRenderObject();
Offset localPosition =
referenceBox.globalToLocal(details.globalPosition);
setState(() {
points = new List.from(points)..add(localPosition);
});
},
onPanEnd: (DragEndDetails details) => points.add(null),
),
new Text('data'),
CustomPaint(
painter:
new SignaturePainter(points, canvas, widget.pictureRecorder)),
new Text('data')
],
);
}
}
///The main class which display the first Canvas, Drawing/Signig area
///
///Floating action button used to stop the PictureRecorder's recording,
///then send the Picture to the next view/Canvas which should display it
class DemoApp extends StatelessWidget {
Signature sign = new Signature();
Widget build(BuildContext context) => new Scaffold(
body: sign,
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.save),
onPressed: () async {
ui.Picture picture = sign.pictureRecorder.endRecording();
Navigator.push(context, new MaterialPageRoute(builder: (context) => new SecondCanvasView(picture)));
},
),
);
}
void main() => runApp(new MaterialApp(home: new DemoApp()));
最佳答案
首先,感谢您提出有趣的问题和独立的答案。这令人耳目一新,而且更容易帮助解决问题!
您的代码存在一些问题。首先是您不应该将 Canvas 和点从 Signature 传递到 SignatureState;这是 flutter 中的反模式。
这里的代码有效。我也做了一些对清理它的答案来说不必要的小事情,对此很抱歉 =D。
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
///This class is use just to check if the Image returned by
///the PictureRecorder of the first Canvas is not empty.
class CanvasImageDraw extends CustomPainter {
ui.Image image;
CanvasImageDraw(this.image);
@override
void paint(ui.Canvas canvas, ui.Size size) {
// simple aspect fit for the image
var hr = size.height / image.height;
var wr = size.width / image.width;
double ratio;
double translateX;
double translateY;
if (hr < wr) {
ratio = hr;
translateX = (size.width - (ratio * image.width)) / 2;
translateY = 0.0;
} else {
ratio = wr;
translateX = 0.0;
translateY = (size.height - (ratio * image.height)) / 2;
}
canvas.translate(translateX, translateY);
canvas.scale(ratio, ratio);
canvas.drawImage(image, new Offset(0.0, 0.0), new Paint());
}
@override
bool shouldRepaint(CanvasImageDraw oldDelegate) {
return image != oldDelegate.image;
}
}
///Class used to display the second canvas
class SecondView extends StatelessWidget {
ui.Image image;
var pictureRecorder = new ui.PictureRecorder();
SecondView({this.image});
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Image Debug'),
),
body: new Column(
children: <Widget>[
new Text('Top'),
CustomPaint(
painter: new CanvasImageDraw(image),
size: new Size(200.0, 200.0),
),
new Text('Bottom'),
],
));
}
}
///This is the CustomPainter of the first Canvas which is used
///to draw to display the users draw/sign.
class SignaturePainter extends CustomPainter {
final List<Offset> points;
final int revision;
SignaturePainter(this.points, [this.revision = 0]);
void paint(canvas, Size size) {
if (points.length < 2) return;
Paint paint = new Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null)
canvas.drawLine(points[i], points[i + 1], paint);
}
}
// simplified this, but if you ever modify points instead of changing length you'll
// have to revise.
bool shouldRepaint(SignaturePainter other) => other.revision != revision;
}
///Classes used to get the user draw/sign, send it to the first canvas,
///then display it.
///'points' list of position returned by the GestureDetector
class Signature extends StatefulWidget {
Signature({Key key}): super(key: key);
@override
State<StatefulWidget> createState()=> new SignatureState();
}
class SignatureState extends State<Signature> {
List<Offset> _points = <Offset>[];
int _revision = 0;
ui.Image get rendered {
var pictureRecorder = new ui.PictureRecorder();
Canvas canvas = new Canvas(pictureRecorder);
SignaturePainter painter = new SignaturePainter(_points);
var size = context.size;
// if you pass a smaller size here, it cuts off the lines
painter.paint(canvas, size);
// if you use a smaller size for toImage, it also cuts off the lines - so I've
// done that in here as well, as this is the only place it's easy to get the width & height.
return pictureRecorder.endRecording().toImage(size.width.floor(), size.height.floor());
}
void _addToPoints(Offset position) {
_revision++;
_points.add(position);
}
Widget build(BuildContext context) {
return new Stack(
children: [
GestureDetector(
onPanStart: (DragStartDetails details) {
RenderBox referenceBox = context.findRenderObject();
Offset localPosition = referenceBox.globalToLocal(details.globalPosition);
setState(() {
_addToPoints(localPosition);
});
},
onPanUpdate: (DragUpdateDetails details) {
RenderBox referenceBox = context.findRenderObject();
Offset localPosition = referenceBox.globalToLocal(details.globalPosition);
setState(() {
_addToPoints(localPosition);
});
},
onPanEnd: (DragEndDetails details) => setState(() => _addToPoints(null)),
),
CustomPaint(painter: new SignaturePainter(_points, _revision)),
],
);
}
}
///The main class which display the first Canvas, Drawing/Signing area
///
///Floating action button used to stop the PictureRecorder's recording,
///then send the Picture to the next view/Canvas which should display it
class DemoApp extends StatelessWidget {
GlobalKey<SignatureState> signatureKey = new GlobalKey();
Widget build(BuildContext context) => new Scaffold(
body: new Signature(key: signatureKey),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.save),
onPressed: () async {
var image = signatureKey.currentState.rendered;
Navigator.push(context, new MaterialPageRoute(builder: (context) => new SecondView(image: image)));
},
),
);
}
void main() => runApp(new MaterialApp(home: new DemoApp()));
您遇到的最大问题是您到处都是自己的 canvas
对象 - 这不是您应该做的事情。 CustomPainter 提供自己的 Canvas 。你画的时候,我觉得不是真的画对地方了。
此外,每次添加一个点时都会重新创建列表。我假设那是因为由于 other.points != points
,您的 Canvas 不会以其他方式绘制。我已将其更改为采用修订 int,每次将某些内容添加到列表时该修订 int 都会递增。更好的方法是使用自己的列表子类,它自己完成此操作,但这对于这个示例来说有点过分了 =D。如果您确定永远不会修改列表中的任何元素,您也可以只使用 other.points.length != points.length
。
考虑到图像的大小,还有一些事情要做。我走了最简单的路线,使 Canvas 与其父级大小相同,这样渲染就更容易了,因为它可以再次使用相同的大小(因此渲染图像的大小与手机屏幕相同) ).如果您不想这样做而是渲染自己的尺寸,则可以做到。您必须向 SignaturePainter 传递一些内容,这样它才能知道如何缩放点,使它们适合新的大小(如果您愿意,可以为此调整 CanvasImageDraw 中的纵横比适合代码)。
如果您对我所做的还有任何其他问题,请随时提问。
关于image - 使用 PictureRecorder 保存 Canvas 的图片导致图像为空,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49929983/