123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795 |
- import 'dart:async';
- import 'dart:convert';
- import 'package:camera/camera.dart';
- import 'package:fis_jsonrpc/rpc.dart';
- import 'package:flutter/foundation.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:get/get.dart';
- import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
- import 'package:image_gallery_saver/image_gallery_saver.dart';
- import 'package:vitalapp/architecture/storage/storage.dart';
- import 'package:vitalapp/architecture/utils/prompt_box.dart';
- import 'package:vitalapp/managers/interfaces/cache.dart';
- import 'package:fis_common/logger/logger.dart';
- import 'package:vitalapp/rpc.dart';
- import 'package:vitalapp/store/store.dart';
- import 'dart:ui' as ui;
- import 'index.dart';
- class IdCardScanController extends GetxController with WidgetsBindingObserver {
- IdCardScanController();
- final state = IdCardScanState();
- List<CameraDescription> _cameras = <CameraDescription>[];
- List<CameraDescription> get cameras => _cameras;
- CameraController? kCameraController;
- double _minAvailableExposureOffset = 0.0;
- double _maxAvailableExposureOffset = 0.0;
- double _minAvailableZoom = 1.0;
- double _maxAvailableZoom = 1.0;
- double _currentScale = 1.0;
- double _baseScale = 1.0;
- // 屏幕上手指数量
- int pointers = 0;
- // 当前身份证信息
- IdCardInfoModel idCardInfo = IdCardInfoModel();
- /// 开始缩放
- void handleScaleStart(ScaleStartDetails details) {
- _baseScale = _currentScale;
- }
- /// 当前捕获帧的人脸列表
- List<Face> kFrameFacesResult = [];
- /// 当前捕获帧大小
- Size kFrameImageSize = Size.zero;
- /// 缩放更新
- Future<void> handleScaleUpdate(ScaleUpdateDetails details) async {
- // When there are not exactly two fingers on screen don't scale
- if (kCameraController == null || pointers != 2) {
- return;
- }
- // 屏蔽缩放
- // _currentScale = (_baseScale * details.scale)
- // .clamp(_minAvailableZoom, _maxAvailableZoom);
- // await kCameraController!.setZoomLevel(_currentScale);
- }
- /// 修改对焦点 [暂不执行]
- void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) {
- if (kCameraController == null) {
- return;
- }
- final CameraController cameraController = kCameraController!;
- final Offset offset = Offset(
- details.localPosition.dx / constraints.maxWidth,
- details.localPosition.dy / constraints.maxHeight,
- );
- cameraController.setExposurePoint(offset);
- cameraController.setFocusPoint(offset);
- }
- /// 初始化相机
- Future<void> initAvailableCameras() async {
- try {
- _cameras = await availableCameras();
- if (_cameras.isNotEmpty) {
- // state.isCameraReady = true;
- }
- // print("cameras: ${_cameras.length}");
- } on CameraException catch (e) {
- logger.e("cameras: ${e.code} ${e.description}");
- }
- }
- /// 启动指定相机
- Future<void> openNewCamera(CameraDescription cameraDescription) async {
- final CameraController? oldController = kCameraController;
- if (oldController != null) {
- // `kCameraController` needs to be set to null before getting disposed,
- // to avoid a race condition when we use the kCameraController that is being
- // disposed. This happens when camera permission dialog shows up,
- // which triggers `didChangeAppLifecycleState`, which disposes and
- // re-creates the kCameraController.
- kCameraController = null;
- await oldController.dispose();
- }
- final CameraController cameraController = CameraController(
- cameraDescription,
- ResolutionPreset.max,
- enableAudio: false,
- imageFormatGroup: ImageFormatGroup.jpeg,
- );
- kCameraController = cameraController;
- // If the kCameraController is updated then update the UI.
- cameraController.addListener(() {
- if (cameraController.value.hasError) {
- PromptBox.toast(
- "Camera error ${cameraController.value.errorDescription}");
- }
- });
- try {
- await cameraController.initialize();
- await Future.wait(<Future<Object?>>[
- // The exposure mode is currently not supported on the web.
- ...!kIsWeb
- ? <Future<Object?>>[
- cameraController.getMinExposureOffset().then(
- (double value) => _minAvailableExposureOffset = value),
- cameraController
- .getMaxExposureOffset()
- .then((double value) => _maxAvailableExposureOffset = value)
- ]
- : <Future<Object?>>[],
- cameraController
- .getMaxZoomLevel()
- .then((double value) => _maxAvailableZoom = value),
- cameraController
- .getMinZoomLevel()
- .then((double value) => _minAvailableZoom = value),
- ]);
- } on CameraException catch (e) {
- switch (e.code) {
- case 'CameraAccessDenied':
- PromptBox.toast('You have denied camera access.');
- break;
- case 'CameraAccessDeniedWithoutPrompt':
- // iOS only
- PromptBox.toast('Please go to Settings app to enable camera access.');
- break;
- case 'CameraAccessRestricted':
- // iOS only
- PromptBox.toast('Camera access is restricted.');
- break;
- case 'AudioAccessDenied':
- PromptBox.toast('You have denied audio access.');
- break;
- case 'AudioAccessDeniedWithoutPrompt':
- // iOS only
- PromptBox.toast('Please go to Settings app to enable audio access.');
- break;
- case 'AudioAccessRestricted':
- // iOS only
- PromptBox.toast('Audio access is restricted.');
- break;
- default:
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- break;
- }
- }
- }
- /// 遍历当前相机列表并启动后置相机
- void openBackCamera() async {
- if (_cameras.isEmpty) {
- PromptBox.toast('Error: No cameras found.');
- } else {
- for (CameraDescription cameraDescription in _cameras) {
- if (cameraDescription.lensDirection == CameraLensDirection.back) {
- await openNewCamera(cameraDescription);
- lockCaptureOrientation();
- update();
- state.isCameraReady = true;
- break;
- }
- }
- }
- }
- /// 遍历当前相机列表并启动前置相机
- void openFrontCamera() async {
- if (_cameras.isEmpty) {
- PromptBox.toast('Error: No cameras found.');
- } else {
- for (CameraDescription cameraDescription in _cameras) {
- if (cameraDescription.lensDirection == CameraLensDirection.front) {
- await openNewCamera(cameraDescription);
- lockCaptureOrientation();
- update();
- state.isCameraReady = true;
- break;
- }
- }
- }
- }
- /// 相机锁定旋转
- Future<void> lockCaptureOrientation() async {
- final CameraController? cameraController = kCameraController;
- if (cameraController == null || !cameraController.value.isInitialized) {
- PromptBox.toast('Error: select a camera first.');
- return;
- }
- if (!cameraController.value.isCaptureOrientationLocked) {
- try {
- await cameraController
- .lockCaptureOrientation(DeviceOrientation.landscapeLeft);
- } on CameraException catch (e) {
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- }
- } else {
- PromptBox.toast('Rotation lock is already enabled.');
- }
- }
- /// 执行一次拍摄
- Future<XFile?> takePicture() async {
- final CameraController? cameraController = kCameraController;
- if (cameraController == null || !cameraController.value.isInitialized) {
- PromptBox.toast('Error: select a camera first.');
- return null;
- }
- if (cameraController.value.isTakingPicture) {
- // A capture is already pending, do nothing.
- return null;
- }
- try {
- final XFile file = await cameraController.takePicture();
- return file;
- } on CameraException catch (e) {
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- return null;
- }
- }
- /// 测试图像文件缓存,print 遍历输出
- void debugShowCache() async {
- final cacheManager = Get.find<ICacheManager>();
- double cacheSize = await cacheManager.getCacheSize();
- double imageCacheSize = await cacheManager.getImageCacheSize();
- debugPrint('cacheSize = $cacheSize : ${formatSize(cacheSize)}');
- debugPrint(
- 'imageCacheSize = $imageCacheSize : ${formatSize(imageCacheSize)}');
- }
- /// 文件大小转为可读 Str
- static String formatSize(double value) {
- List<String> unitArr = ['B', 'K', 'M', 'G'];
- int index = 0;
- while (value > 1024) {
- index++;
- value = value / 1024;
- }
- String size = value.toStringAsFixed(2);
- return size + unitArr[index];
- }
- /// 保存到相册
- void saveImageToGallery(XFile image) async {
- // 获取图像的字节数据
- Uint8List bytes = await image.readAsBytes();
- // 将图像保存到相册
- await ImageGallerySaver.saveImage(bytes, quality: 100);
- }
- /// 处理图像裁切
- Future<String> clipLocalImage(XFile soureceImage, double scale) async {
- assert(scale >= 1, 'scale must be greater than 1');
- // 获取图像的字节数据
- Uint8List bytes = await soureceImage.readAsBytes();
- var codec = await ui.instantiateImageCodec(bytes);
- var nextFrame = await codec.getNextFrame();
- var image = nextFrame.image;
- Rect src = Rect.fromLTWH(
- (scale - 1) / 2 / scale * image.width.toDouble(),
- (scale - 1) / 2 / scale * image.height.toDouble(),
- image.width.toDouble() / scale,
- image.height.toDouble() / scale,
- );
- Rect dst =
- Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble());
- ui.Image croppedImage = await getCroppedImage(image, src, dst);
- ByteData? newImageBytes =
- await croppedImage.toByteData(format: ui.ImageByteFormat.png);
- if (newImageBytes == null) {
- return '';
- }
- Uint8List newImageUint8List = newImageBytes.buffer.asUint8List();
- /// FIXME 不要存到相册而是存到临时目录
- Map<Object?, Object?> result =
- await ImageGallerySaver.saveImage(newImageUint8List, quality: 100);
- String jsonString = jsonEncode(result);
- Map<String, dynamic> json = jsonDecode(jsonString);
- String? filePath = json['filePath'];
- return filePath ?? '';
- }
- /// 获取图像文件的图像尺寸
- Future<Size> getImageSize(XFile soureceImage) async {
- // 获取图像的字节数据
- Uint8List bytes = await soureceImage.readAsBytes();
- var codec = await ui.instantiateImageCodec(bytes);
- var nextFrame = await codec.getNextFrame();
- var image = nextFrame.image;
- return Size(image.width.toDouble(), image.height.toDouble());
- }
- /// 获取裁切后的图像
- Future<ui.Image> getCroppedImage(ui.Image image, Rect src, Rect dst) {
- var pictureRecorder = ui.PictureRecorder();
- Canvas canvas = Canvas(pictureRecorder);
- canvas.drawImageRect(image, src, dst, Paint());
- return pictureRecorder.endRecording().toImage(
- dst.width.floor(),
- dst.height.floor(),
- );
- }
- /// 发生拍摄身份证事件
- void onCaptureIdCardButtonPressed() {
- takePicture().then((XFile? file) async {
- // imageFile = file;
- if (file != null) {
- // await clipLocalImage(file, 1.8);
- final url = await rpc.storage.upload(
- file,
- fileType: 'png',
- );
- PatientBaseDTO result = await rpc.patient.getPatientBaseByImageAsync(
- GetPatientBaseByImageRequest(
- token: Store.user.token,
- image: url,
- ),
- );
- /// TODO 上传给server,获取返回值信息
- if (result.isSuccess) {
- PromptBox.toast('身份证识别成功');
- idCardInfo.localCardImagePath = file.path;
- idCardInfo.idCardName = result.patientName ?? "";
- idCardInfo.idCardGender =
- result.patientGender == GenderEnum.Male ? "男" : "女";
- idCardInfo.idCardNation = result.nationality ?? "";
- idCardInfo.idCardBirthDate = result.birthday.toString();
- idCardInfo.idCardAddress = result.patientAddress ?? "";
- idCardInfo.idCardNumber = result.cardNo ?? "";
- state.isShowIdCardInfoSwitch = true;
- state.isIdCardInfoShow = true;
- state.isInIdCardScan = true;
- openFrontCamera();
- }
- debugShowCache();
- }
- });
- }
- /// 发生开始人脸识别事件
- void onCaptureFaceButtonPressed() {
- // runDetectionTimer();
- rpcTest2();
- }
- /// 人脸录入测试
- void rpcTest2() async {
- if (kCameraController == null) {
- return;
- }
- final XFile? file = await takePicture();
- if (file != null) {
- faceDetector = FaceDetector(options: FaceDetectorOptions());
- // faceDetector =
- // FaceDetector(options: FaceDetectorOptions(enableContours: true));
- int faceNum = 1; // max 分辨率下检测用时大约 100ms
- // int faceNum =
- // await doDetection(faceDetector, file.path); // max 分辨率下检测用时大约 100ms
- if (faceNum == 0) {
- PromptBox.toast('请将面部保持在识别框内');
- return;
- } else if (faceNum > 1) {
- PromptBox.toast('请保持只有一张面部在识别范围内');
- return;
- }
- /// TODO 上传图像到云然后传给后端
- final url = await rpc.storage.upload(
- file,
- fileType: 'png',
- );
- print('⭐⭐⭐⭐⭐⭐⭐⭐ url: $url');
- try {
- SavePersonDTO result =
- await rpc.patient.savePatientBaseByFaceImageAsync(
- SavePatientBaseByFaceImageRequest(
- cardNo: idCardInfo.idCardNumber,
- token: Store.user.token,
- image: url,
- ),
- );
- print(result);
- if (result.success) {
- PromptBox.toast('人脸数据存入成功');
- } else {
- PromptBox.toast('人脸数据存入失败: ${result.errMessage}');
- }
- } catch (e) {
- logger.e("savePatientBaseByFaceImageAsync failed: $e", e);
- }
- // if (timer.tick == 1 || kFrameImageSize == Size.zero) {
- // Size imageSize = await getImageSize(file);
- // kFrameImageSize = imageSize;
- // }
- // int kTime = DateTime.now().millisecondsSinceEpoch;
- // print('⭐⭐⭐⭐⭐⭐⭐⭐ capture time: ${kTime - lastCaptureTime} ms');
- // lastCaptureTime = kTime;
- // /// 记录用时 ms
- // int endTime = DateTime.now().millisecondsSinceEpoch;
- // print('⭐⭐⭐⭐⭐⭐⭐⭐ detection time: ${endTime - lastCaptureTime} ms');
- // update(['face_bounding_box']);
- // if (timer.tick >= 10) {
- // finishFaceDetection(); // TODO 接入真实的判断条件
- // }
- }
- }
- /// 发生结束录制视频事件
- void onStopButtonPressed() {
- stopVideoRecording().then((XFile? file) {
- if (file != null) {
- PromptBox.toast('Video recorded to ${file.path}');
- // videoFile = file;
- // _startVideoPlayer();
- }
- update();
- });
- }
- /// 发生开始录制视频事件
- void onVideoRecordButtonPressed() {
- startVideoRecording().then((_) {
- update();
- });
- }
- /// 暂停录制视频
- void onPauseButtonPressed() {
- pauseVideoRecording().then((_) {
- update();
- });
- }
- /// 恢复视频录制
- void onResumeButtonPressed() {
- resumeVideoRecording().then((_) {
- update();
- });
- }
- /// 开始录制视频
- Future<void> startVideoRecording() async {
- final CameraController? cameraController = kCameraController;
- if (cameraController == null || !cameraController.value.isInitialized) {
- PromptBox.toast('Error: select a camera first.');
- return;
- }
- if (cameraController.value.isRecordingVideo) {
- // A recording is already started, do nothing.
- return;
- }
- try {
- await cameraController.startVideoRecording();
- } on CameraException catch (e) {
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- return;
- }
- }
- /// 停止录制视频
- Future<XFile?> stopVideoRecording() async {
- final CameraController? cameraController = kCameraController;
- if (cameraController == null || !cameraController.value.isRecordingVideo) {
- return null;
- }
- try {
- return cameraController.stopVideoRecording();
- } on CameraException catch (e) {
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- return null;
- }
- }
- /// 暂停录制视频
- Future<void> pauseVideoRecording() async {
- final CameraController? cameraController = kCameraController;
- if (cameraController == null || !cameraController.value.isRecordingVideo) {
- return;
- }
- try {
- await cameraController.pauseVideoRecording();
- } on CameraException catch (e) {
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- rethrow;
- }
- }
- /// 恢复视频录制
- Future<void> resumeVideoRecording() async {
- final CameraController? cameraController = kCameraController;
- if (cameraController == null || !cameraController.value.isRecordingVideo) {
- return;
- }
- try {
- await cameraController.resumeVideoRecording();
- } on CameraException catch (e) {
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- rethrow;
- }
- }
- /// 在 widget 内存中分配后立即调用。
- @override
- void onInit() async {
- // await initCamera();
- super.onInit();
- WidgetsBinding.instance.addObserver(this);
- }
- /// 在 onInit() 之后调用 1 帧。这是进入的理想场所
- @override
- void onReady() async {
- super.onReady();
- await initAvailableCameras();
- openBackCamera();
- }
- /// 在 [onDelete] 方法之前调用。
- @override
- void onClose() {
- super.onClose();
- final cacheManager = Get.find<ICacheManager>();
- cacheManager.clearApplicationImageCache();
- closeDetector();
- WidgetsBinding.instance.removeObserver(this);
- final CameraController? cameraController = kCameraController;
- if (cameraController != null) {
- kCameraController = null;
- cameraController.dispose();
- }
- }
- /// dispose 释放内存
- @override
- void dispose() {
- super.dispose();
- }
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) async {
- super.didChangeAppLifecycleState(state);
- final CameraController? cameraController = kCameraController;
- // App state changed before we got the chance to initialize.
- if (cameraController == null || !cameraController.value.isInitialized) {
- return;
- }
- if (state == AppLifecycleState.inactive) {
- cameraController.dispose();
- this.state.isCameraReady = false;
- } else if (state == AppLifecycleState.resumed) {
- await openNewCamera(cameraController.description);
- this.state.isCameraReady = true;
- }
- }
- /// 完成人脸识别
- void finishFaceDetection() {
- final result = IdCardScanResult(
- success: true,
- cardNo: idCardInfo.idCardNumber,
- name: idCardInfo.idCardName,
- nation: idCardInfo.idCardNation,
- gender:
- idCardInfo.idCardGender == '男' ? GenderEnum.Male : GenderEnum.Female,
- birthday: DateTime.now(),
- address: idCardInfo.idCardAddress,
- );
- Get.back<IdCardScanResult>(
- result: result,
- );
- }
- /// WIP
- /// 面部识别 基于 Google's ML Kit
- ///
- InputImage inputImage = InputImage.fromFilePath('');
- FaceDetector faceDetector = FaceDetector(options: FaceDetectorOptions());
- // 进行一次人脸检测
- Future<void> doDetection(
- FaceDetector faceDetector,
- String imagePath,
- ) async {
- inputImage = InputImage.fromFilePath(imagePath);
- // inputImage = image;
- final List<Face> faces = await faceDetector.processImage(inputImage);
- kFrameFacesResult = [];
- kFrameFacesResult.addAll(faces);
- // for (Face face in faces) {
- // final Rect boundingBox = face.boundingBox;
- // final double? rotX =
- // face.headEulerAngleX; // Head is tilted up and down rotX degrees
- // final double? rotY =
- // face.headEulerAngleY; // Head is rotated to the right rotY degrees
- // final double? rotZ =
- // face.headEulerAngleZ; // Head is tilted sideways rotZ degrees
- // // If landmark detection was enabled with FaceDetectorOptions (mouth, ears,
- // // eyes, cheeks, and nose available):
- // final FaceLandmark? leftEar = face.landmarks[FaceLandmarkType.leftEar];
- // if (leftEar != null) {
- // final Point<int> leftEarPos = leftEar.position;
- // }
- // // If classification was enabled with FaceDetectorOptions:
- // if (face.smilingProbability != null) {
- // final double? smileProb = face.smilingProbability;
- // }
- // // If face tracking was enabled with FaceDetectorOptions:
- // if (face.trackingId != null) {
- // final int? id = face.trackingId;
- // }
- // }
- }
- // bool isDetectionRunning = false;
- Timer? _detectionTimer;
- /// 开始持续检测人脸
- void runDetectionTimer() {
- if (_detectionTimer != null) {
- _detectionTimer!.cancel();
- _detectionTimer = null;
- faceDetector.close();
- state.isShowIdCardScanResult = false;
- return;
- }
- faceDetector =
- FaceDetector(options: FaceDetectorOptions(enableContours: true));
- state.isShowIdCardScanResult = true;
- /// 记录最后一次拍摄的时间
- int lastCaptureTime = DateTime.now().millisecondsSinceEpoch;
- _detectionTimer = Timer.periodic(
- const Duration(milliseconds: 300), // max 分辨率下拍摄用时大约 500ms-800ms
- (timer) async {
- if (kCameraController == null) {
- return;
- }
- final XFile? file = await takePicture();
- if (file != null) {
- if (timer.tick == 1 || kFrameImageSize == Size.zero) {
- Size imageSize = await getImageSize(file);
- kFrameImageSize = imageSize;
- }
- int kTime = DateTime.now().millisecondsSinceEpoch;
- print('⭐⭐⭐⭐⭐⭐⭐⭐ capture time: ${kTime - lastCaptureTime} ms');
- lastCaptureTime = kTime;
- /// 记录用时 ms
- await doDetection(faceDetector, file.path); // max 分辨率下检测用时大约 100ms
- int endTime = DateTime.now().millisecondsSinceEpoch;
- print('⭐⭐⭐⭐⭐⭐⭐⭐ detection time: ${endTime - lastCaptureTime} ms');
- update(['face_bounding_box']);
- if (timer.tick >= 10) {
- finishFaceDetection(); // TODO 接入真实的判断条件
- }
- }
- },
- );
- }
- /// 用于将读取的视频流传给 Google ML
- InputImage cameraImageToInputImage(CameraImage cameraImage) {
- return InputImage.fromBytes(
- bytes: _concatenatePlanes(cameraImage.planes),
- metadata: InputImageMetadata(
- size: Size(cameraImage.width.toDouble(), cameraImage.height.toDouble()),
- rotation: InputImageRotation.rotation0deg,
- format: _getInputImageFormat(cameraImage.format.group),
- bytesPerRow: cameraImage.planes[0].bytesPerRow,
- ),
- );
- }
- /// 辅助函数,将CameraImage的plane组合为Uint8List格式
- Uint8List _concatenatePlanes(List<Plane> planes) {
- final WriteBuffer allBytes = WriteBuffer();
- for (Plane plane in planes) {
- allBytes.putUint8List(plane.bytes);
- }
- return allBytes.done().buffer.asUint8List();
- }
- InputImageFormat _getInputImageFormat(ImageFormatGroup format) {
- switch (format) {
- case ImageFormatGroup.yuv420:
- return InputImageFormat.yuv420;
- case ImageFormatGroup.bgra8888:
- return InputImageFormat.bgra8888;
- default:
- throw ArgumentError('Invalid image format');
- }
- }
- /// 销毁检测器
- void closeDetector() {
- if (_detectionTimer != null) {
- state.isShowIdCardScanResult = false;
- _detectionTimer!.cancel();
- _detectionTimer = null;
- }
- faceDetector.close();
- }
- }
- class IdCardScanResult {
- bool success;
- /// 身份证号
- String cardNo;
- /// 姓名
- String name;
- /// 性别
- GenderEnum gender;
- /// 民族
- String nation;
- /// 出生日期
- DateTime birthday;
- /// 地址
- String address;
- IdCardScanResult({
- required this.success,
- required this.cardNo,
- required this.name,
- required this.gender,
- required this.nation,
- required this.birthday,
- required this.address,
- });
- }
|