doc_preview.dart 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import 'dart:io';
  2. import 'dart:math' as math;
  3. import 'package:camera/camera.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:focus_detector/focus_detector.dart';
  6. import 'package:image_size_getter/file_input.dart';
  7. import 'package:image_size_getter/image_size_getter.dart';
  8. import 'package:native_vision/edge_detection.dart';
  9. import 'package:native_vision/polygon_anim.dart';
  10. import 'package:path_provider/path_provider.dart';
  11. import 'dart:ui' as ui;
  12. import '../native/doc_scanner.dart';
  13. class _DocIndicator extends CustomPainter {
  14. // final Paint _paint = Paint()..color = Colors.yellow.withOpacity(0.5);
  15. final Paint _polygonPaint = Paint()
  16. ..color = Colors.blue.withOpacity(0.4)
  17. // ..style = PaintingStyle.stroke
  18. ..strokeWidth = 2.0;
  19. late List<Offset> _coords;
  20. _DocIndicator(List<Offset> coords) {
  21. _coords = coords;
  22. }
  23. @override
  24. void paint(Canvas canvas, ui.Size size) {
  25. // canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), _paint);
  26. Path path = Path();
  27. path.addPolygon(_coords, true);
  28. path.close();
  29. canvas.drawPath(path, _polygonPaint);
  30. }
  31. @override
  32. bool shouldRepaint(covariant CustomPainter oldDelegate) {
  33. return true;
  34. }
  35. }
  36. class DocPreview extends StatefulWidget {
  37. final double displayW, displayH;
  38. const DocPreview({Key? key, required this.displayW, required this.displayH})
  39. : super(key: key);
  40. @override
  41. State<StatefulWidget> createState() {
  42. return DocPreviewState();
  43. }
  44. }
  45. class DocPreviewState extends State<DocPreview>
  46. with SingleTickerProviderStateMixin {
  47. bool detecting = false;
  48. final _coordsNotifier = ValueNotifier<List<Offset>>(List.empty());
  49. late ROI previewROI;
  50. CameraController? _cameraController;
  51. final CameraLensDirection _direction = CameraLensDirection.back;
  52. late DocScanner _docScan;
  53. late PolygonAnimController _polygonAnim;
  54. Future<CameraDescription> _getCamera(CameraLensDirection dir) async {
  55. final avaibleCameras = await availableCameras();
  56. return avaibleCameras[0];
  57. // return await availableCameras().then(
  58. // (List<CameraDescription> cameras) => cameras.lastWhere(
  59. // (CameraDescription camera) => camera.lensDirection == dir,
  60. // ),
  61. // );
  62. }
  63. ROI _calculateRoi(ui.Size sizeInPixel) {
  64. var screenSize = MediaQuery.of(context).size;
  65. final screenH = math.max(screenSize.height, screenSize.width);
  66. final screenW = math.min(screenSize.height, screenSize.width);
  67. final previewH = math.max(sizeInPixel.height, sizeInPixel.width);
  68. final previewW = math.min(sizeInPixel.height, sizeInPixel.width);
  69. final screenRatio = screenH / screenW;
  70. final previewRatio = previewH / previewW;
  71. // final maxHeight =
  72. // screenRatio > previewRatio ? screenH : screenW / previewW * previewH;
  73. // final maxWidth =
  74. // screenRatio > previewRatio ? screenH / previewH * previewW : screenW;
  75. final maxWidth = widget.displayW;
  76. final maxHeight = maxWidth * previewRatio;
  77. final scale = maxHeight / previewH;
  78. final top = (maxHeight - widget.displayH) / 2 / scale;
  79. final left = (maxWidth - widget.displayW) / 2 / scale;
  80. final cropH = widget.displayH / scale;
  81. final cropW = widget.displayW / scale;
  82. return ROI.create(left.toInt(), top.toInt(), cropW.toInt(), cropH.toInt(),
  83. maxWidth, maxHeight);
  84. }
  85. Future<Map<String, dynamic>?> takePic() async {
  86. try {
  87. final file = await _cameraController?.takePicture();
  88. if (file != null) {
  89. final size = ImageSizeGetter.getSize(FileInput(File(file.path)));
  90. final roi = _calculateRoi(
  91. ui.Size(size.width.toDouble(), size.height.toDouble()));
  92. final tmpDir = (await getTemporaryDirectory()).path;
  93. final outPath =
  94. "$tmpDir/roi_${DateTime.now().millisecondsSinceEpoch}.jpg";
  95. final highPreciseDocScan = DocScanner();
  96. await highPreciseDocScan.init(0.85);
  97. final ret =
  98. await highPreciseDocScan.singleScan(file.path, outPath, roi);
  99. await highPreciseDocScan.release();
  100. if (ret == null) return null;
  101. // return {
  102. // "EdgeDetectionResult": EdgeDetectionResult(
  103. // topLeft: Offset(roi.left / imgW, roi.top / imgH),
  104. // topRight: Offset((roi.left + roi.width) / imgW, roi.top / imgH),
  105. // bottomRight: Offset(
  106. // (roi.left + roi.width) / imgW, (roi.top + roi.height) / imgH),
  107. // bottomLeft: Offset(roi.left / imgW, (roi.top + roi.height) / imgH),
  108. // ),
  109. // "ImgPath": file.path,
  110. // };
  111. return {
  112. "EdgeDetectionResult": ret,
  113. "ImgPath": outPath,
  114. };
  115. }
  116. } catch (e, st) {
  117. debugPrintStack(stackTrace: st);
  118. }
  119. return null;
  120. }
  121. _initializeCamera() async {
  122. if (_cameraController != null && _cameraController!.value.isInitialized) {
  123. return;
  124. }
  125. try {
  126. _cameraController = CameraController(
  127. await _getCamera(_direction), ResolutionPreset.photo);
  128. await _cameraController?.initialize();
  129. if (!mounted) {
  130. return;
  131. }
  132. final previewSize = _cameraController!.value.previewSize;
  133. previewROI =
  134. _calculateRoi(ui.Size(previewSize!.width, previewSize.height));
  135. await _cameraController?.startImageStream((CameraImage image) async {
  136. // Stopwatch stopwatch = Stopwatch()..start();
  137. if (detecting || !mounted) return;
  138. detecting = true;
  139. final coords = await _docScan.scanWithCameraImage(
  140. image,
  141. previewROI.left.toInt(),
  142. previewROI.top.toInt(),
  143. previewROI.width.toInt(),
  144. previewROI.height.toInt(),
  145. widget.displayW.toInt(),
  146. widget.displayH.toInt());
  147. if (mounted && coords != null) {
  148. _polygonAnim.startAnim(coords);
  149. }
  150. detecting = false;
  151. // print('doSomething() executed in ${stopwatch.elapsed}');
  152. });
  153. setState(() {});
  154. } catch (e) {
  155. if (e is CameraException) {
  156. debugPrint(e.description);
  157. return;
  158. }
  159. debugPrint(e.toString());
  160. }
  161. }
  162. void onCoordsChange(List<Offset> coords) {
  163. _coordsNotifier.value = coords;
  164. }
  165. @override
  166. void initState() {
  167. super.initState();
  168. _initializeCamera();
  169. _polygonAnim = PolygonAnimController(
  170. len: 4, vsync: this, duration: const Duration(milliseconds: 100));
  171. _polygonAnim.onCoordsChange = onCoordsChange;
  172. _docScan = DocScanner();
  173. _docScan.init(0.65);
  174. }
  175. @override
  176. void dispose() {
  177. _polygonAnim.dispose();
  178. _docScan.release();
  179. _coordsNotifier.dispose();
  180. super.dispose();
  181. }
  182. @override
  183. Widget build(BuildContext context) {
  184. return FocusDetector(
  185. onFocusLost: () {
  186. _cameraController?.dispose();
  187. _cameraController = null;
  188. },
  189. onFocusGained: () {
  190. _initializeCamera();
  191. },
  192. child:
  193. _cameraController == null || !_cameraController!.value.isInitialized
  194. ? Container()
  195. : Stack(children: [
  196. SizedBox(
  197. width: widget.displayW,
  198. height: widget.displayH,
  199. child: ClipRRect(
  200. child: OverflowBox(
  201. maxHeight: previewROI.maxHeight,
  202. maxWidth: previewROI.maxWidth,
  203. child: CameraPreview(
  204. _cameraController!,
  205. ),
  206. ),
  207. ),
  208. ),
  209. ValueListenableBuilder(
  210. valueListenable: _coordsNotifier,
  211. builder: (_, List<Offset> coords, __) {
  212. if (coords.length != 4) return Container();
  213. return CustomPaint(
  214. size: ui.Size(widget.displayW, widget.displayH),
  215. painter: _DocIndicator(coords),
  216. );
  217. })
  218. ]),
  219. );
  220. }
  221. }