import 'dart:io'; import 'dart:math' as math; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:focus_detector/focus_detector.dart'; import 'package:image_size_getter/file_input.dart'; import 'package:image_size_getter/image_size_getter.dart'; import 'package:native_vision/edge_detection.dart'; import 'package:native_vision/polygon_anim.dart'; import 'package:path_provider/path_provider.dart'; import 'dart:ui' as ui; import '../native/doc_scanner.dart'; class _DocIndicator extends CustomPainter { // final Paint _paint = Paint()..color = Colors.yellow.withOpacity(0.5); final Paint _polygonPaint = Paint() ..color = Colors.blue.withOpacity(0.4) // ..style = PaintingStyle.stroke ..strokeWidth = 2.0; late List _coords; _DocIndicator(List coords) { _coords = coords; } @override void paint(Canvas canvas, ui.Size size) { // canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), _paint); Path path = Path(); path.addPolygon(_coords, true); path.close(); canvas.drawPath(path, _polygonPaint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } } class DocPreview extends StatefulWidget { final double displayW, displayH; const DocPreview({Key? key, required this.displayW, required this.displayH}) : super(key: key); @override State createState() { return DocPreviewState(); } } class DocPreviewState extends State with SingleTickerProviderStateMixin { bool detecting = false; final _coordsNotifier = ValueNotifier>(List.empty()); late ROI previewROI; CameraController? _cameraController; final CameraLensDirection _direction = CameraLensDirection.back; late DocScanner _docScan; late PolygonAnimController _polygonAnim; Future _getCamera(CameraLensDirection dir) async { final avaibleCameras = await availableCameras(); return avaibleCameras[0]; // return await availableCameras().then( // (List cameras) => cameras.lastWhere( // (CameraDescription camera) => camera.lensDirection == dir, // ), // ); } ROI _calculateRoi(ui.Size sizeInPixel) { var screenSize = MediaQuery.of(context).size; final screenH = math.max(screenSize.height, screenSize.width); final screenW = math.min(screenSize.height, screenSize.width); final previewH = math.max(sizeInPixel.height, sizeInPixel.width); final previewW = math.min(sizeInPixel.height, sizeInPixel.width); final screenRatio = screenH / screenW; final previewRatio = previewH / previewW; // final maxHeight = // screenRatio > previewRatio ? screenH : screenW / previewW * previewH; // final maxWidth = // screenRatio > previewRatio ? screenH / previewH * previewW : screenW; final maxWidth = widget.displayW; final maxHeight = maxWidth * previewRatio; final scale = maxHeight / previewH; final top = (maxHeight - widget.displayH) / 2 / scale; final left = (maxWidth - widget.displayW) / 2 / scale; final cropH = widget.displayH / scale; final cropW = widget.displayW / scale; return ROI.create(left.toInt(), top.toInt(), cropW.toInt(), cropH.toInt(), maxWidth, maxHeight); } Future?> takePic() async { try { final file = await _cameraController?.takePicture(); if (file != null) { final size = ImageSizeGetter.getSize(FileInput(File(file.path))); final roi = _calculateRoi( ui.Size(size.width.toDouble(), size.height.toDouble())); final tmpDir = (await getTemporaryDirectory()).path; final outPath = "$tmpDir/roi_${DateTime.now().millisecondsSinceEpoch}.jpg"; final highPreciseDocScan = DocScanner(); await highPreciseDocScan.init(0.85); final ret = await highPreciseDocScan.singleScan(file.path, outPath, roi); await highPreciseDocScan.release(); if (ret == null) return null; // return { // "EdgeDetectionResult": EdgeDetectionResult( // topLeft: Offset(roi.left / imgW, roi.top / imgH), // topRight: Offset((roi.left + roi.width) / imgW, roi.top / imgH), // bottomRight: Offset( // (roi.left + roi.width) / imgW, (roi.top + roi.height) / imgH), // bottomLeft: Offset(roi.left / imgW, (roi.top + roi.height) / imgH), // ), // "ImgPath": file.path, // }; return { "EdgeDetectionResult": ret, "ImgPath": outPath, }; } } catch (e, st) { debugPrintStack(stackTrace: st); } return null; } _initializeCamera() async { if (_cameraController != null && _cameraController!.value.isInitialized) { return; } try { _cameraController = CameraController( await _getCamera(_direction), ResolutionPreset.photo); await _cameraController?.initialize(); if (!mounted) { return; } final previewSize = _cameraController!.value.previewSize; previewROI = _calculateRoi(ui.Size(previewSize!.width, previewSize.height)); await _cameraController?.startImageStream((CameraImage image) async { // Stopwatch stopwatch = Stopwatch()..start(); if (detecting || !mounted) return; detecting = true; final coords = await _docScan.scanWithCameraImage( image, previewROI.left.toInt(), previewROI.top.toInt(), previewROI.width.toInt(), previewROI.height.toInt(), widget.displayW.toInt(), widget.displayH.toInt()); if (mounted && coords != null) { _polygonAnim.startAnim(coords); } detecting = false; // print('doSomething() executed in ${stopwatch.elapsed}'); }); setState(() {}); } catch (e) { if (e is CameraException) { debugPrint(e.description); return; } debugPrint(e.toString()); } } void onCoordsChange(List coords) { _coordsNotifier.value = coords; } @override void initState() { super.initState(); _initializeCamera(); _polygonAnim = PolygonAnimController( len: 4, vsync: this, duration: const Duration(milliseconds: 100)); _polygonAnim.onCoordsChange = onCoordsChange; _docScan = DocScanner(); _docScan.init(0.65); } @override void dispose() { _polygonAnim.dispose(); _docScan.release(); _coordsNotifier.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return FocusDetector( onFocusLost: () { _cameraController?.dispose(); _cameraController = null; }, onFocusGained: () { _initializeCamera(); }, child: _cameraController == null || !_cameraController!.value.isInitialized ? Container() : Stack(children: [ SizedBox( width: widget.displayW, height: widget.displayH, child: ClipRRect( child: OverflowBox( maxHeight: previewROI.maxHeight, maxWidth: previewROI.maxWidth, child: CameraPreview( _cameraController!, ), ), ), ), ValueListenableBuilder( valueListenable: _coordsNotifier, builder: (_, List coords, __) { if (coords.length != 4) return Container(); return CustomPaint( size: ui.Size(widget.displayW, widget.displayH), painter: _DocIndicator(coords), ); }) ]), ); } }