feat: Add document scanner package

This commit is contained in:
Anton Stubenbord
2023-02-22 18:17:50 +01:00
parent a8a41b38a8
commit 9c5a45f329
105 changed files with 3998 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
class CameraView extends StatelessWidget {
const CameraView({super.key, required this.controller});
final CameraController controller;
@override
Widget build(BuildContext context) {
if (!controller.value.isInitialized) {
return Container();
}
return Center(
child: CameraPreview(controller),
);
}
}

View File

@@ -0,0 +1,85 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:paperless_document_scanner/types/edge_detection_result.dart';
import 'edge_detection_shape/edge_detection_shape.dart';
class ImagePreview extends StatefulWidget {
const ImagePreview({
super.key,
required this.imagePath,
required this.edgeDetectionResult,
});
final String imagePath;
final EdgeDetectionResult? edgeDetectionResult;
@override
State<ImagePreview> createState() => _ImagePreviewState();
}
class _ImagePreviewState extends State<ImagePreview> {
GlobalKey imageWidgetKey = GlobalKey();
@override
Widget build(BuildContext mainContext) {
return Center(
child: Stack(
fit: StackFit.expand,
children: <Widget>[
const Center(child: Text('Loading ...')),
Image.file(File(widget.imagePath),
fit: BoxFit.contain, key: imageWidgetKey),
FutureBuilder<ui.Image>(
future: loadUiImage(widget.imagePath),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: Text("Loading..."),
);
} else {
return _getEdgePaint(snapshot.data!, context);
}
}),
],
),
);
}
Widget _getEdgePaint(
ui.Image image,
BuildContext context,
) {
if (widget.edgeDetectionResult == null) return Container();
final keyContext = imageWidgetKey.currentContext;
if (keyContext == null) {
return Container();
}
final box = keyContext.findRenderObject() as RenderBox;
return EdgeDetectionShape(
originalImageSize: Size(
image.width.toDouble(),
image.height.toDouble(),
),
renderedImageSize: Size(box.size.width, box.size.height),
edgeDetectionResult: widget.edgeDetectionResult!,
);
}
Future<ui.Image> loadUiImage(String imageAssetPath) async {
final Uint8List data = await File(imageAssetPath).readAsBytes();
final Completer<ui.Image> completer = Completer();
ui.decodeImageFromList(Uint8List.view(data.buffer), (ui.Image image) {
return completer.complete(image);
});
return completer.future;
}
}

View File

@@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
class AnimatedTouchBubblePart extends StatefulWidget {
AnimatedTouchBubblePart({
required this.dragging,
required this.size,
});
final bool dragging;
final double size;
@override
_AnimatedTouchBubblePartState createState() =>
_AnimatedTouchBubblePartState();
}
class _AnimatedTouchBubblePartState extends State<AnimatedTouchBubblePart>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Color?> _colorAnimation;
late Animation<double> _sizeAnimation;
@override
void didChangeDependencies() {
_controller = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_sizeAnimation = Tween<double>(begin: 0.5, end: 1.0).animate(_controller);
_colorAnimation = ColorTween(
begin: Theme.of(context).colorScheme.primary,
end: Theme.of(context).colorScheme.primary,
).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.5, 1.0),
),
);
_controller.repeat();
super.didChangeDependencies();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Center(
child: Container(
width: widget.dragging ? 0 : widget.size / 2,
height: widget.dragging ? 0 : widget.size / 2,
decoration: BoxDecoration(
color: Theme.of(context).accentColor.withOpacity(0.5),
borderRadius: widget.dragging
? BorderRadius.circular(widget.size)
: BorderRadius.circular(widget.size / 4)))),
AnimatedBuilder(
builder: (context, child) {
return Center(
child: Container(
width: widget.dragging
? 0
: widget.size * _sizeAnimation.value,
height: widget.dragging
? 0
: widget.size * _sizeAnimation.value,
decoration: BoxDecoration(
border: Border.all(
color: _colorAnimation.value ?? Colors.transparent,
width: widget.size / 20),
borderRadius: widget.dragging
? BorderRadius.zero
: BorderRadius.circular(
widget.size * _sizeAnimation.value / 2))));
},
animation: _controller,
)
],
);
}
}

View File

@@ -0,0 +1,213 @@
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:paperless_document_scanner/paperless_document_scanner.dart';
import 'package:paperless_document_scanner/types/edge_detection_result.dart';
import 'edge_painter.dart';
import 'magnifier.dart' as m;
import 'touch_bubble.dart';
class EdgeDetectionShape extends StatefulWidget {
const EdgeDetectionShape({
super.key,
required this.renderedImageSize,
required this.originalImageSize,
required this.edgeDetectionResult,
});
final Size renderedImageSize;
final Size originalImageSize;
final EdgeDetectionResult edgeDetectionResult;
@override
State<EdgeDetectionShape> createState() => _EdgeDetectionShapeState();
}
class _EdgeDetectionShapeState extends State<EdgeDetectionShape> {
late double edgeDraggerSize;
List<Offset> points = [];
late Offset _topLeft;
late Offset _topRight;
late Offset _bottomLeft;
late Offset _bottomRight;
late double renderedImageWidth;
late double renderedImageHeight;
late double top;
late double left;
Offset? currentDragPosition;
@override
void didChangeDependencies() {
double shortestSide = min(
MediaQuery.of(context).size.width, MediaQuery.of(context).size.height);
edgeDraggerSize = shortestSide / 12;
super.didChangeDependencies();
}
@override
void initState() {
top = 0.0;
left = 0.0;
_topLeft = widget.edgeDetectionResult.topLeft;
_topRight = widget.edgeDetectionResult.topRight;
_bottomLeft = widget.edgeDetectionResult.bottomLeft;
_bottomRight = widget.edgeDetectionResult.bottomRight;
double widthFactor =
widget.renderedImageSize.width / widget.originalImageSize.width;
double heightFactor =
widget.renderedImageSize.height / widget.originalImageSize.height;
double sizeFactor = min(widthFactor, heightFactor);
renderedImageHeight = widget.originalImageSize.height * sizeFactor;
top = ((widget.renderedImageSize.height - renderedImageHeight) / 2);
renderedImageWidth = widget.originalImageSize.width * sizeFactor;
left = ((widget.renderedImageSize.width - renderedImageWidth) / 2);
super.initState();
}
@override
Widget build(BuildContext context) {
return m.Magnifier(
visible: currentDragPosition != null,
position: currentDragPosition ?? Offset.zero,
child: Stack(
children: [
_buildTouchBubbles(),
CustomPaint(
painter: EdgePainter(
points: points,
color: Theme.of(context).colorScheme.primary.withOpacity(0.5),
),
)
],
),
);
}
Offset _getNewPositionAfterDrag(Offset position) {
return Offset(
position.dx / renderedImageWidth,
position.dy / renderedImageHeight,
);
}
Offset _clampOffset(Offset givenOffset) {
double absoluteX = givenOffset.dx * renderedImageWidth;
double absoluteY = givenOffset.dy * renderedImageHeight;
return Offset(absoluteX.clamp(0.0, renderedImageWidth) / renderedImageWidth,
absoluteY.clamp(0.0, renderedImageHeight) / renderedImageHeight);
}
Widget _buildTouchBubbles() {
points = [
Offset(
left + _topLeft.dx * renderedImageWidth,
top + _topLeft.dy * renderedImageHeight,
),
Offset(
left + _topRight.dx * renderedImageWidth,
top + _topRight.dy * renderedImageHeight,
),
Offset(
left + _bottomRight.dx * renderedImageWidth,
top + _bottomRight.dy * renderedImageHeight,
),
Offset(
left + _bottomLeft.dx * renderedImageWidth,
top + _bottomLeft.dy * renderedImageHeight,
),
Offset(
left + _topLeft.dx * renderedImageWidth,
top + _topLeft.dy * renderedImageHeight,
),
];
return SizedBox(
width: widget.renderedImageSize.width,
height: widget.renderedImageSize.height,
child: Stack(
children: [
Positioned(
left: points[0].dx - (edgeDraggerSize / 2),
top: points[0].dy - (edgeDraggerSize / 2),
child: TouchBubble(
size: edgeDraggerSize,
onDragFinished: () => setState(() => currentDragPosition = null),
onDrag: (position) {
setState(
() {
currentDragPosition = Offset(points[0].dx, points[0].dy);
_topLeft = _clampOffset(
widget.edgeDetectionResult.topLeft +
_getNewPositionAfterDrag(position),
);
},
);
},
),
),
Positioned(
left: points[1].dx - (edgeDraggerSize / 2),
top: points[1].dy - (edgeDraggerSize / 2),
child: TouchBubble(
size: edgeDraggerSize,
onDrag: (position) {
setState(() {
currentDragPosition = Offset(points[1].dx, points[1].dy);
_topRight = _clampOffset(
widget.edgeDetectionResult.topRight +
_getNewPositionAfterDrag(position),
);
});
},
onDragFinished: () => setState(() => currentDragPosition = null),
),
),
Positioned(
left: points[2].dx - (edgeDraggerSize / 2),
top: points[2].dy - (edgeDraggerSize / 2),
child: TouchBubble(
size: edgeDraggerSize,
onDrag: (position) {
setState(() {
currentDragPosition = Offset(points[2].dx, points[2].dy);
_bottomRight = _clampOffset(
widget.edgeDetectionResult.bottomRight +
_getNewPositionAfterDrag(position),
);
});
},
onDragFinished: () => setState(() => currentDragPosition = null),
),
),
Positioned(
left: points[3].dx - (edgeDraggerSize / 2),
top: points[3].dy - (edgeDraggerSize / 2),
child: TouchBubble(
size: edgeDraggerSize,
onDrag: (position) {
setState(() {
_bottomLeft = _clampOffset(
widget.edgeDetectionResult.bottomLeft +
_getNewPositionAfterDrag(position),
);
currentDragPosition = Offset(points[3].dx, points[3].dy);
});
},
onDragFinished: () => setState(() => currentDragPosition = null),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,25 @@
import 'dart:ui';
import 'package:flutter/material.dart';
class EdgePainter extends CustomPainter {
EdgePainter({required this.points, required this.color});
final List<Offset> points;
final Color color;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color.withOpacity(0.5)
..strokeWidth = 2
..strokeCap = StrokeCap.round;
canvas.drawPoints(PointMode.polygon, points, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}

View File

@@ -0,0 +1,100 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'magnifier_painter.dart';
class Magnifier extends StatefulWidget {
const Magnifier({
super.key,
required this.child,
required this.position,
this.visible = true,
this.scale = 1.5,
this.size = const Size(160, 160),
});
final Widget child;
final Offset position;
final bool visible;
final double scale;
final Size size;
@override
_MagnifierState createState() => _MagnifierState();
}
class _MagnifierState extends State<Magnifier> {
late Size _magnifierSize;
late double _scale;
late Matrix4 _matrix;
@override
void initState() {
_magnifierSize = widget.size;
_scale = widget.scale;
_calculateMatrix();
super.initState();
}
@override
void didUpdateWidget(Magnifier oldWidget) {
super.didUpdateWidget(oldWidget);
_calculateMatrix();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
widget.child,
if (widget.visible && widget.position != null) _getMagnifier(context)
],
);
}
void _calculateMatrix() {
if (widget.position == null) {
return;
}
setState(() {
double newX = widget.position.dx - (_magnifierSize.width / 2 / _scale);
double newY = widget.position.dy - (_magnifierSize.height / 2 / _scale);
final Matrix4 updatedMatrix = Matrix4.identity()
..scale(_scale, _scale)
..translate(-newX, -newY);
_matrix = updatedMatrix;
});
}
Widget _getMagnifier(BuildContext context) {
return Align(
alignment: _getAlignment(),
child: ClipOval(
child: BackdropFilter(
filter: ImageFilter.matrix(_matrix.storage),
child: CustomPaint(
painter: MagnifierPainter(color: Theme.of(context).accentColor),
size: _magnifierSize,
),
),
),
);
}
Alignment _getAlignment() {
if (_bubbleCrossesMagnifier()) {
return Alignment.topRight;
}
return Alignment.topLeft;
}
bool _bubbleCrossesMagnifier() =>
widget.position.dx < widget.size.width &&
widget.position.dy < widget.size.height;
}

View File

@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
class MagnifierPainter extends CustomPainter {
const MagnifierPainter({required this.color, this.strokeWidth = 5});
final double strokeWidth;
final Color color;
@override
void paint(Canvas canvas, Size size) {
_drawCircle(canvas, size);
_drawCrosshair(canvas, size);
}
void _drawCircle(Canvas canvas, Size size) {
Paint paintObject = Paint()
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..color = color;
canvas.drawCircle(
size.center(Offset(0, 0)), size.longestSide / 2, paintObject);
}
void _drawCrosshair(Canvas canvas, Size size) {
Paint crossPaint = Paint()
..strokeWidth = strokeWidth / 2
..color = color;
double crossSize = size.longestSide * 0.04;
canvas.drawLine(size.center(Offset(-crossSize, -crossSize)),
size.center(Offset(crossSize, crossSize)), crossPaint);
canvas.drawLine(size.center(Offset(crossSize, -crossSize)),
size.center(Offset(-crossSize, crossSize)), crossPaint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}

View File

@@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'animated_touch_bubble_part.dart';
class TouchBubble extends StatefulWidget {
const TouchBubble({
super.key,
required this.size,
required this.onDrag,
required this.onDragFinished,
});
final double size;
final Function onDrag;
final Function onDragFinished;
@override
State<TouchBubble> createState() => _TouchBubbleState();
}
class _TouchBubbleState extends State<TouchBubble> {
bool dragging = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onPanStart: _startDragging,
onPanUpdate: _drag,
onPanCancel: _cancelDragging,
onPanEnd: (_) => _cancelDragging(),
child: Container(
width: widget.size,
height: widget.size,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(widget.size / 2)),
child: AnimatedTouchBubblePart(
dragging: dragging,
size: widget.size,
)));
}
void _startDragging(DragStartDetails data) {
setState(() {
dragging = true;
});
widget
.onDrag(data.localPosition - Offset(widget.size / 2, widget.size / 2));
}
void _cancelDragging() {
setState(() {
dragging = false;
});
widget.onDragFinished();
}
void _drag(DragUpdateDetails data) {
if (!dragging) {
return;
}
widget.onDrag(data.delta);
}
}

View File

@@ -0,0 +1,130 @@
import 'dart:async';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:paperless_document_scanner/paperless_document_scanner.dart';
import 'package:paperless_document_scanner/types/edge_detection_result.dart';
class EdgeDetector {
static Future<void> startEdgeDetectionFromFileIsolate(
EdgeDetectionFromFileInput edgeDetectionInput,
) async {
EdgeDetectionResult result =
await EdgeDetection.detectEdgesFromFile(edgeDetectionInput.inputPath);
edgeDetectionInput.sendPort.send(result);
}
static Future<void> startEdgeDetectionIsolate(
EdgeDetectionInput edgeDetectionInput,
) async {
EdgeDetectionResult result =
await EdgeDetection.detectEdges(edgeDetectionInput.bytes);
edgeDetectionInput.sendPort.send(result);
}
static Future<void> processImageIsolate(
ProcessImageInput processImageInput) async {
ImageProcessing.processImage(
processImageInput.bytes,
processImageInput.edgeDetectionResult,
);
processImageInput.sendPort.send(true);
}
Future<EdgeDetectionResult> detectEdgesFromFile(String filePath) async {
final port = ReceivePort();
_spawnIsolate<EdgeDetectionInput>(
startEdgeDetectionIsolate,
EdgeDetectionFromFileInput(
inputPath: filePath,
sendPort: port.sendPort,
),
port,
);
return await _subscribeToPort<EdgeDetectionResult>(port);
}
Future<bool> processImageFromFile(
String filePath, EdgeDetectionResult edgeDetectionResult) async {
final port = ReceivePort();
_spawnIsolate<ProcessImageInput>(
processImageIsolate,
ProcessImageFromFileInput(
inputPath: filePath,
edgeDetectionResult: edgeDetectionResult,
sendPort: port.sendPort),
port);
return await _subscribeToPort<bool>(port);
}
void _spawnIsolate<T>(
void Function(T) function,
dynamic input,
ReceivePort port,
) {
Isolate.spawn<T>(function, input,
onError: port.sendPort, onExit: port.sendPort);
}
Future<T> _subscribeToPort<T>(ReceivePort port) async {
late StreamSubscription sub;
var completer = Completer<T>();
sub = port.listen((result) async {
print(result);
await sub.cancel();
completer.complete(await result);
});
return completer.future;
}
}
class EdgeDetectionFromFileInput {
EdgeDetectionFromFileInput({
required this.inputPath,
required this.sendPort,
});
final String inputPath;
final SendPort sendPort;
}
class EdgeDetectionInput {
EdgeDetectionInput({
required this.bytes,
required this.sendPort,
});
final Uint8List bytes;
final SendPort sendPort;
}
class ProcessImageInput {
ProcessImageInput({
required this.bytes,
required this.edgeDetectionResult,
required this.sendPort,
});
final Uint8List bytes;
final EdgeDetectionResult edgeDetectionResult;
final SendPort sendPort;
}
class ProcessImageFromFileInput {
ProcessImageFromFileInput({
required this.inputPath,
required this.edgeDetectionResult,
required this.sendPort,
});
final String inputPath;
final EdgeDetectionResult edgeDetectionResult;
final SendPort sendPort;
}

View File

@@ -0,0 +1,25 @@
import 'dart:io';
import 'package:flutter/material.dart';
class ImageView extends StatefulWidget {
const ImageView({super.key, required this.imagePath});
final String imagePath;
@override
State<ImageView> createState() => _ImageViewState();
}
class _ImageViewState extends State<ImageView> {
GlobalKey imageWidgetKey = GlobalKey();
@override
Widget build(BuildContext mainContext) {
return Center(
child: Image.file(
File(widget.imagePath),
fit: BoxFit.contain,
),
);
}
}

View File

@@ -0,0 +1,113 @@
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image/image.dart' as imglib;
import 'scan.dart';
late final List<CameraDescription> cameras;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
cameras = await availableCameras();
runApp(const EdgeDetectionApp());
}
class EdgeDetectionApp extends StatefulWidget {
const EdgeDetectionApp({super.key});
@override
State<EdgeDetectionApp> createState() => _EdgeDetectionAppState();
}
class _EdgeDetectionAppState extends State<EdgeDetectionApp> {
CameraImage? _image;
late final CameraController _controller;
@override
void initState() {
super.initState();
() async {
_controller = CameraController(
cameras
.where(
(element) => element.lensDirection == CameraLensDirection.back)
.first,
ResolutionPreset.low,
enableAudio: false,
);
await _controller.initialize();
_controller.startImageStream((image) {
setState(() => _image = image);
});
}();
}
Uint8List concatenatePlanes(List<Plane> planes) {
final WriteBuffer allBytes = WriteBuffer();
for (final plane in planes) {
allBytes.putUint8List(plane.bytes);
}
return allBytes.done().buffer.asUint8List();
}
Image convertYUV420toImageColor(CameraImage image) {
final int width = image.width;
final int height = image.height;
final int uvRowStride = image.planes[1].bytesPerRow;
final int uvPixelStride = image.planes[1].bytesPerPixel!;
print("uvRowStride: " + uvRowStride.toString());
print("uvPixelStride: " + uvPixelStride.toString());
// imgLib -> Image package from https://pub.dartlang.org/packages/image
var img = imglib.Image(
width: width,
height: height,
); // Create Image buffer
// Fill image buffer with plane[0] from YUV420_888
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
final int uvIndex =
uvPixelStride * (x / 2).floor() + uvRowStride * (y / 2).floor();
final int index = y * width + x;
final yp = image.planes[0].bytes[index];
final up = image.planes[1].bytes[uvIndex];
final vp = image.planes[2].bytes[uvIndex];
// Calculate pixel color
int r = (yp + vp * 1436 / 1024 - 179).round().clamp(0, 255);
int g = (yp - up * 46549 / 131072 + 44 - vp * 93604 / 131072 + 91)
.round()
.clamp(0, 255);
int b = (yp + up * 1814 / 1024 - 227).round().clamp(0, 255);
// color: 0x FF FF FF FF
// A B G R
img.data?.setPixelRgb(x, y, r, g, b);
}
}
imglib.PngEncoder pngEncoder = new imglib.PngEncoder(level: 0);
final png = pngEncoder.encode(img);
return Image.memory(png);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Scaffold(
body: Center(
child: _image != null
? convertYUV420toImageColor(_image!)
: Placeholder(),
),
),
);
}
}

View File

@@ -0,0 +1,174 @@
import 'dart:async';
import 'dart:developer';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:paperless_document_scanner/types/edge_detection_result.dart';
import 'camera_view.dart';
import 'cropping_preview.dart';
import 'edge_detector.dart';
import 'image_view.dart';
class Scan extends StatefulWidget {
const Scan({
super.key,
required,
required this.cameras,
});
final List<CameraDescription> cameras;
@override
State<Scan> createState() => _ScanState();
}
class _ScanState extends State<Scan> {
late final CameraController controller;
String? imagePath;
String? croppedImagePath;
EdgeDetectionResult? edgeDetectionResult;
@override
void initState() {
super.initState();
controller = CameraController(
widget.cameras[0],
ResolutionPreset.veryHigh,
imageFormatGroup: ImageFormatGroup.jpeg,
enableAudio: false,
);
() async {
await controller.initialize();
log(controller.value.toString());
}();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
_getMainWidget(),
_getBottomBar(),
],
),
);
}
Widget _getMainWidget() {
if (croppedImagePath != null) {
return ImageView(imagePath: croppedImagePath!);
}
if (imagePath == null && edgeDetectionResult == null) {
return CameraView(controller: controller);
}
return ImagePreview(
imagePath: imagePath!,
edgeDetectionResult: edgeDetectionResult,
);
}
Widget _getButtonRow() {
if (imagePath != null) {
return Align(
alignment: Alignment.bottomCenter,
child: FloatingActionButton(
child: Icon(Icons.check),
onPressed: () async {
if (croppedImagePath == null) {
return _processImage(imagePath!, edgeDetectionResult!);
}
setState(() {
imagePath = null;
edgeDetectionResult = null;
croppedImagePath = null;
});
},
),
);
}
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton(
foregroundColor: Colors.white,
child: Icon(Icons.camera_alt),
onPressed: onTakePictureButtonPressed,
),
],
);
}
String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();
Future<String> takePicture() async {
if (!controller.value.isInitialized) {
throw Exception("Select camera first!");
}
final file = await controller.takePicture();
return file.path;
}
Future _detectEdges(String filePath) async {
if (!mounted) {
return;
}
setState(() {
imagePath = filePath;
});
EdgeDetectionResult result =
await EdgeDetector().detectEdgesFromFile(filePath);
setState(() {
edgeDetectionResult = result;
});
}
Future _processImage(
String filePath, EdgeDetectionResult edgeDetectionResult) async {
if (!mounted) {
return;
}
bool result = await EdgeDetector()
.processImageFromFile(filePath, edgeDetectionResult);
if (result == false) {
return;
}
setState(() {
imageCache.clearLiveImages();
imageCache.clear();
croppedImagePath = imagePath;
});
}
void onTakePictureButtonPressed() async {
String filePath = await takePicture();
log('Picture saved to $filePath');
await _detectEdges(filePath);
}
Padding _getBottomBar() {
return Padding(
padding: EdgeInsets.only(bottom: 32),
child: Align(alignment: Alignment.bottomCenter, child: _getButtonRow()),
);
}
}