I want to give users the chance to create a polygon with their mobile phone on a CustomPaint
. For this feature there is also a GestureDetector
involved. The image is being loaded from a file with the ImagePicker
library. The problem here is how to resize the canvas and gesture detector to the size of the picture. Any ideas?
Code: `import 'dart:io';
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
import 'package:listassist/services/auth.dart';
class PictureShow extends StatefulWidget {
@override
_PictureShowState createState() => _PictureShowState();
}
class _PictureShowState extends State<PictureShow> {
ui.Image _image;
Image _imageWidget;
List<ui.Offset> _points = [ui.Offset(90, 120), ui.Offset(90, 370), ui.Offset(320, 370), ui.Offset(320, 120)];
bool _clear = false;
int _currentlyDraggedIndex = -1;
@override
Widget build(BuildContext context) {
final AppBar appBar = AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
title: Text("Rechungserkennung"),
);
return Scaffold(
floatingActionButton:
FloatingActionButton(
child: Icon(Icons.clear),
onPressed: () {
setState(() {
_clear = true;
_points = [];
});
}
),
appBar: appBar,
backgroundColor: _imageWidget != null ? Colors.black : Colors.white,
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
if (_imageWidget == null) ...[
FlatButton(
onPressed: () => _pickImage(ImageSource.camera),
color: Colors.blueAccent,
padding: EdgeInsets.all(40.0),
child: Column(
children: <Widget>[
Icon(Icons.camera_alt, color: Colors.white,),
Text("Aus der Kamera", style: TextStyle(color: Colors.white),)
],
),
),
Padding(
padding: const EdgeInsets.only(top: 40, bottom: 40),
child: Text("oder", textScaleFactor: 2,),
),
FlatButton(
onPressed: () => _pickImage(ImageSource.gallery),
color: Colors.brown,
padding: EdgeInsets.all(40.0),
child: Column(
children: <Widget>[
Icon(Icons.photo, color: Colors.white,),
Text("Aus der Gallerie", style: TextStyle(color: Colors.white),)
],
),
),
],
if (_imageWidget != null) ...[
GestureDetector(
onPanStart: (DragStartDetails details) {
// get distance from points to check if is in circle
int indexMatch = -1;
for (int i = 0; i < _points.length; i++) {
double distance = sqrt(pow(details.localPosition.dx - _points[i].dx, 2) + pow(details.localPosition.dy - _points[i].dy, 2));
if (distance <= 30) {
indexMatch = i;
break;
}
}
if (indexMatch != -1) {
_currentlyDraggedIndex = indexMatch;
}
},
onPanUpdate: (DragUpdateDetails details) {
if (_currentlyDraggedIndex != -1) {
setState(() {
_points = List.from(_points);
_points[_currentlyDraggedIndex] = details.localPosition;
});
}
},
onPanEnd: (_) {
setState(() {
_currentlyDraggedIndex = -1;
});
},
child: CustomPaint(
size: Size.fromHeight(MediaQuery.of(context).size.height - appBar.preferredSize.height),
painter: RectanglePainter(points: _points, clear: _clear, image: _image),
),
)
]
],
),
);
}
Future _pickImage(ImageSource imageSource) async {
try {
File imageFile = await ImagePicker.pickImage(source: imageSource);
ui.Image finalImg = await _load(imageFile.path);
setState(() {
_imageWidget = Image.file(imageFile);
_image = finalImg;
});
} on Exception {
ResultHandler
.showInfoSnackbar(Text("Ein Fehler ist aufgetreten beim Öffnen des Bildes. Die App benötigt Zugriff auf die Galerie"));
}
}
Future<ui.Image> _load(String asset) async {
ByteData data = await rootBundle.load(asset);
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
ui.FrameInfo fi = await codec.getNextFrame();
return fi.image;
}
}
class RectanglePainter extends CustomPainter {
List<Offset> points;
bool clear;
final ui.Image image;
RectanglePainter({@required this.points, @required this.clear, @required this.image});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.red
..strokeCap = StrokeCap.square
..style = PaintingStyle.fill
..strokeWidth = 2;
final outputRect = Rect.fromPoints(ui.Offset.zero, ui.Offset(size.width, size.height));
final Size imageSize = Size(image.width.toDouble(), image.height.toDouble());
final FittedSizes sizes = applyBoxFit(BoxFit.contain, imageSize, outputRect.size);
final Rect inputSubrect = Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
final Rect outputSubrect = Alignment.center.inscribe(sizes.destination, outputRect);
canvas.drawImageRect(image, inputSubrect, outputSubrect, paint);
if (!clear) {
final circlePaint = Paint()
..color = Colors.red
..strokeCap = StrokeCap.square
..style = PaintingStyle.fill
..blendMode = BlendMode.multiply
..strokeWidth = 2;
for (int i = 0; i < points.length; i++) {
if (i + 1 == points.length) {
canvas.drawLine(points[i], points[0], paint);
} else {
canvas.drawLine(points[i], points[i + 1], paint);
}
canvas.drawCircle(points[i], 10, circlePaint);
}
}
}
@override
bool shouldRepaint(RectanglePainter oldPainter) => oldPainter.points != points || clear ;
}`
How it currently looks like: (You can see the canvas goes beyond. If I log the coordinates emitted by the Gesture Detector they're also above 0 even when clicking outside of the picture. The green border shows how it SHOULD be)