Commit 28eeb459 by Chokmongkhon Jansanom

feat: match face.

parent f584375e
...@@ -30,6 +30,9 @@ public class LuxandSdk { ...@@ -30,6 +30,9 @@ public class LuxandSdk {
libCode = FSDK.Initialize(); libCode = FSDK.Initialize();
} }
availableTemplates = new ArrayList<>();
availableTemplates.add(new FaceTemplate("nax", new byte[]{1, 2}, new byte[]{1, 2}));
return libCode; return libCode;
} }
...@@ -134,7 +137,11 @@ public class LuxandSdk { ...@@ -134,7 +137,11 @@ public class LuxandSdk {
if (tracker == null) { if (tracker == null) {
tracker = new FSDK.HTracker(); tracker = new FSDK.HTracker();
FSDK.CreateTracker(tracker); int libCode = FSDK.CreateTracker(tracker);
if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Failed to CreateTracker: " + libCode);
}
} }
mode = ELuxandMode.MatchTemplate; mode = ELuxandMode.MatchTemplate;
...@@ -146,12 +153,14 @@ public class LuxandSdk { ...@@ -146,12 +153,14 @@ public class LuxandSdk {
return; return;
} }
int libCode = FSDK.FreeTracker(tracker); int libCode = FSDK.ClearTracker(tracker);
tracker = null; tracker = null;
mode = ELuxandMode.Detection; mode = ELuxandMode.Detection;
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Failed to FreeTracker: " + libCode); Log.e("LUXAND", "Failed to FreeTracker: " + libCode);
} else {
Log.e("LUXAND", "=== TRACKER CLEARED ===");
} }
} }
...@@ -159,11 +168,7 @@ public class LuxandSdk { ...@@ -159,11 +168,7 @@ public class LuxandSdk {
availableTemplates = templates; availableTemplates = templates;
} }
public static List<FaceMatch> TrackFaceForMatchTemplate(byte[] buffer) throws InvalidObjectException { public static List<FaceMatch> TrackFaceForMatchTemplate(byte[] buffer, List<FaceTemplate> templates) {
if (availableTemplates == null) {
throw new InvalidObjectException("No available templates.");
}
ValidateMatchTemplate(); ValidateMatchTemplate();
List<FaceMatch> result = new ArrayList<>(); List<FaceMatch> result = new ArrayList<>();
...@@ -181,63 +186,71 @@ public class LuxandSdk { ...@@ -181,63 +186,71 @@ public class LuxandSdk {
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Failed to feed frame: " + libCode); Log.e("LUXAND", "Failed to feed frame: " + libCode);
FSDK.FreeImage(image);
return result; return result;
} }
FSDK.TFacePosition pos = new FSDK.TFacePosition(); FSDK.TFacePosition pos = new FSDK.TFacePosition();
for (int i = 0; i < ids.length; i++) { for (int i = 0; i < ids.length; i++) {
libCode = FSDK.GetTrackerFacePosition(tracker, CAMERA_ID, ids[i], pos); if (ids[i] == 0) {
if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Failed to get tracker face position: " + libCode);
continue; continue;
} }
FSDK.FSDK_Features eyes = new FSDK.FSDK_Features(); libCode = FSDK.GetTrackerFacePosition(tracker, CAMERA_ID, ids[i], pos);
libCode = FSDK.DetectEyes(image, eyes);
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Cannot detect eye: " + libCode); Log.e("LUXAND", "Failed to get tracker face position: " + libCode);
continue; continue;
} }
FSDK.FSDK_FaceTemplate eyeTemplate = new FSDK.FSDK_FaceTemplate(); // FSDK.FSDK_Features eyes = new FSDK.FSDK_Features();
FSDK.GetFaceTemplateUsingEyes(image, eyes, eyeTemplate); // libCode = FSDK.DetectEyes(image, eyes);
//
FSDK.FSDK_Features face = new FSDK.FSDK_Features(); // if (libCode != FSDK.FSDKE_OK) {
libCode = FSDK.DetectFacialFeatures(image, face); // Log.e("LUXAND", "Cannot detect eye: " + libCode);
// continue;
// }
//
// FSDK.FSDK_FaceTemplate eyeTemplate = new FSDK.FSDK_FaceTemplate();
// FSDK.GetFaceTemplateUsingEyes(image, eyes, eyeTemplate);
if (libCode != FSDK.FSDKE_OK) { // FSDK.FSDK_Features face = new FSDK.FSDK_Features();
Log.e("LUXAND", "Cannot detect face: " + libCode); // libCode = FSDK.DetectFacialFeaturesInRegion(image, pos, face);
continue; //
} // if (libCode != FSDK.FSDKE_OK) {
// Log.e("LUXAND", "Cannot DetectFacialFeaturesInRegion: " + libCode);
// continue;
// }
FSDK.FSDK_FaceTemplate faceTemplate = new FSDK.FSDK_FaceTemplate(); FSDK.FSDK_FaceTemplate faceTemplate = new FSDK.FSDK_FaceTemplate();
FSDK.GetFaceTemplateUsingFeatures(image, face, faceTemplate); libCode = FSDK.GetFaceTemplateInRegion(image, pos, faceTemplate);
FSDK.FreeImage(image);
for (int j = 0; j < availableTemplates.size(); j++) {
FSDK.FSDK_FaceTemplate t = new FSDK.FSDK_FaceTemplate();
t.template = availableTemplates.get(j).EyeTemplate;
float[] similarityEye = new float[1];
libCode = FSDK.MatchFaces(eyeTemplate, t, similarityEye);
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Cannot MatchFaces for eye: " + libCode); Log.e("LUXAND", "Cannot GetFaceTemplateUsingFeatures: " + libCode);
continue; continue;
} }
t.template = availableTemplates.get(j).FaceTemplate; for (int j = 0; j < templates.size(); j++) {
FSDK.FSDK_FaceTemplate t = new FSDK.FSDK_FaceTemplate();
// t.template = availableTemplates.get(j).EyeTemplate;
// float[] similarityEye = new float[1];
// libCode = FSDK.MatchFaces(eyeTemplate, t, similarityEye);
//
// if (libCode != FSDK.FSDKE_OK) {
// Log.e("LUXAND", "Cannot MatchFaces for eye: " + libCode);
// continue;
// }
t.template = templates.get(i).FaceTemplate;
float[] similarityFace = new float[1]; float[] similarityFace = new float[1];
libCode = FSDK.MatchFaces(faceTemplate, t, similarityFace); libCode = FSDK.MatchFaces(faceTemplate, t, similarityFace);
if (libCode == FSDK.FSDKE_OK) { if (libCode == FSDK.FSDKE_OK) {
result.add(new FaceMatch( result.add(new FaceMatch(
new FacePosition(pos.xc, pos.yc, pos.w), new FacePosition(pos.xc, pos.yc, pos.w),
availableTemplates.get(i).Id, templates.get(i).Id,
similarityEye[0], 0,
similarityFace[0])); similarityFace[0]));
break; break;
} else { } else {
...@@ -246,13 +259,15 @@ public class LuxandSdk { ...@@ -246,13 +259,15 @@ public class LuxandSdk {
} }
} }
FSDK.FreeImage(image);
return result; return result;
} }
public static int[] TrackFace(byte[] buffer) { public static int[] TrackFace(byte[] buffer) {
ValidateTracker(); ValidateTracker();
int[] result = new int[5]; int[] result = new int[3];
HImage image = new HImage(); HImage image = new HImage();
int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length); int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length);
...@@ -352,7 +367,6 @@ public class LuxandSdk { ...@@ -352,7 +367,6 @@ public class LuxandSdk {
byte[][] templates = new byte[2][]; byte[][] templates = new byte[2][];
HImage image = new HImage(); HImage image = new HImage();
int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length); int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length);
FSDK.FreeImage(image);
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Failed to load image: " + libCode); Log.e("LUXAND", "Failed to load image: " + libCode);
...@@ -383,6 +397,8 @@ public class LuxandSdk { ...@@ -383,6 +397,8 @@ public class LuxandSdk {
FSDK.GetFaceTemplateUsingFeatures(image, face, ft); FSDK.GetFaceTemplateUsingFeatures(image, face, ft);
templates[1] = ft.template; templates[1] = ft.template;
FSDK.FreeImage(image);
return templates; return templates;
} }
} }
......
...@@ -3,6 +3,7 @@ package myhr.facescan.myhr_facescan ...@@ -3,6 +3,7 @@ package myhr.facescan.myhr_facescan
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Matrix import android.graphics.Matrix
import android.util.Log
import androidx.annotation.NonNull import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
...@@ -10,9 +11,13 @@ import io.flutter.plugin.common.MethodCall ...@@ -10,9 +11,13 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.common.MethodChannel.Result
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import myhr.facescan.myhr_facescan.models.FaceTemplate import myhr.facescan.myhr_facescan.models.FaceTemplate
/** MyhrFacescanPlugin */ /** MyhrFacescanPlugin */
...@@ -22,6 +27,9 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler { ...@@ -22,6 +27,9 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler {
/// This local reference serves to register the plugin with the Flutter Engine and unregister it /// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity /// when the Flutter Engine is detached from the Activity
private lateinit var channel: MethodChannel private lateinit var channel: MethodChannel
private var matchJob: Job? = null
private var detectJob: Job? = null
private var attributeJob: Job? = null
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "myhr_facescan") channel = MethodChannel(flutterPluginBinding.binaryMessenger, "myhr_facescan")
...@@ -32,26 +40,19 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler { ...@@ -32,26 +40,19 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler {
if (call.method == "activateLuxand") { if (call.method == "activateLuxand") {
result.success(activateLuxand(call.arguments)) result.success(activateLuxand(call.arguments))
} else if (call.method == "detectFace") { } else if (call.method == "detectFace") {
GlobalScope.launch { detectFace(call.arguments, result)
val found = detectFace(call.arguments)
result.success(found)
}
} else if (call.method == "getFaceTemplate") { } else if (call.method == "getFaceTemplate") {
val templates = getFaceTemplate(call.arguments) val templates = getFaceTemplate(call.arguments)
var s1 = templates.get(0)?.decodeToString() val data: Map<String, ByteArray> = mapOf(
var s2 = templates.get(1)?.decodeToString() "eye" to templates[0],
var stringArrays = arrayListOf(s1, s2) "face" to templates[1]
result.success(stringArrays) )
} else if (call.method == "getFaceAttribute") {
GlobalScope.launch {
val data = getFaceAttribute(call.arguments)
result.success(data) result.success(data)
} } else if (call.method == "getFaceAttribute") {
getFaceAttribute(call.arguments, result)
} else if (call.method == "detectFaceForMatch") { } else if (call.method == "detectFaceForMatch") {
GlobalScope.launch { detectFaceForMatch(call.arguments, result)
val data = detectFaceForMatch(call.arguments)
result.success(data)
}
} else if (call.method == "setAvailableTemplate") { } else if (call.method == "setAvailableTemplate") {
val key = call.arguments as Map<String, String> val key = call.arguments as Map<String, String>
val ids = key["ids"] as List<String> val ids = key["ids"] as List<String>
...@@ -61,8 +62,10 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler { ...@@ -61,8 +62,10 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler {
ids.mapIndexed { index, s -> FaceTemplate(s, eyes.get(index), faces.get(index)) } ids.mapIndexed { index, s -> FaceTemplate(s, eyes.get(index), faces.get(index)) }
LuxandSdk.SetAvailableTempalte(data) LuxandSdk.SetAvailableTempalte(data)
result.success(true)
} else if (call.method == "freeResource") { } else if (call.method == "freeResource") {
LuxandSdk.FreeTracker() LuxandSdk.FreeTracker()
result.success(true)
} else { } else {
result.notImplemented() result.notImplemented()
} }
...@@ -91,24 +94,58 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler { ...@@ -91,24 +94,58 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler {
return imageBytes return imageBytes
} }
private fun detectFace(data: Any): IntArray { private fun detectFace(data: Any, result: Result) {
if (detectJob?.isActive == false || detectJob == null) {
detectJob = CoroutineScope(Dispatchers.Main).launch {
val bytes = withContext(Dispatchers.Default) {
val imageBytes = getImageBytes(data) val imageBytes = getImageBytes(data)
val rotateBitmap = rotateImage(byteArrayToBitmap(imageBytes), 270f) val rotateBitmap = rotateImage(byteArrayToBitmap(imageBytes), 270f)
val bytes = bitmapToByteArray(rotateBitmap, Bitmap.CompressFormat.JPEG, 100) bitmapToByteArray(rotateBitmap, Bitmap.CompressFormat.JPEG, 100)
}
return LuxandSdk.TrackFace(bytes) withContext(Dispatchers.Default) {
val faces = LuxandSdk.TrackFace(bytes)
result.success(faces)
}
}
}
} }
private fun detectFaceForMatch(data: Any): String { private fun detectFaceForMatch(data: Any, result: Result) {
if (matchJob?.isActive == false || matchJob == null) {
matchJob = CoroutineScope(Dispatchers.Main).launch {
val bytes = withContext(Dispatchers.Default) {
val imageBytes = getImageBytes(data) val imageBytes = getImageBytes(data)
val rotateBitmap = rotateImage(byteArrayToBitmap(imageBytes), 270f) val rotateBitmap = rotateImage(byteArrayToBitmap(imageBytes), 270f)
val bytes = bitmapToByteArray(rotateBitmap, Bitmap.CompressFormat.JPEG, 100) bitmapToByteArray(rotateBitmap, Bitmap.CompressFormat.JPEG, 100)
val result = LuxandSdk.TrackFaceForMatchTemplate(bytes) }
return result.map { d -> val key = data as Map<String, String>
d.FaceId + "," + d.SimilarityEye + "," + d.SimilarityFace + "," + val ids = key["ids"] as List<String>
d.Position.CenterX + "," + d.Position.CenterY + "," + d.Position.Width val eyes = key["eyes"] as List<ByteArray>
}.joinToString { ";" } val faces = key["faces"] as List<ByteArray>
val models = mutableListOf<FaceTemplate>()
for (index in ids.indices) {
models.add(FaceTemplate(ids[index], eyes.get(index), faces.get(index)))
}
withContext(Dispatchers.Default) {
val matches = LuxandSdk.TrackFaceForMatchTemplate(bytes, models)
val matchesString = mutableListOf<String>()
for (m in matches) {
matchesString.add(
m.FaceId + "(,)" + m.SimilarityEye + "(,)" + m.SimilarityFace + "(,)" +
m.Position.CenterX + "(,)" + m.Position.CenterY + "(,)" + m.Position.Width
)
}
result.success(matchesString)
}
}
}
} }
private fun getFaceTemplate(data: Any): Array<ByteArray> { private fun getFaceTemplate(data: Any): Array<ByteArray> {
...@@ -120,13 +157,22 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler { ...@@ -120,13 +157,22 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler {
return templates return templates
} }
private fun getFaceAttribute(data: Any): DoubleArray { private fun getFaceAttribute(data: Any, result: Result) {
if (attributeJob?.isActive == false || attributeJob == null) {
attributeJob = CoroutineScope(Dispatchers.Main).launch {
val bytes = withContext(Dispatchers.Default) {
val imageBytes = getImageBytes(data) val imageBytes = getImageBytes(data)
val rotateBitmap = rotateImage(byteArrayToBitmap(imageBytes), 270f) val rotateBitmap = rotateImage(byteArrayToBitmap(imageBytes), 270f)
val result = bitmapToByteArray(rotateBitmap, Bitmap.CompressFormat.JPEG, 100) bitmapToByteArray(rotateBitmap, Bitmap.CompressFormat.JPEG, 100)
}
val key = data as Map<String, String> val key = data as Map<String, String>
val isExpression = key["isExpression"] as Boolean; val isExpression = key["isExpression"] as Boolean;
return LuxandSdk.GetFaceAttribute(result, isExpression) val attr = LuxandSdk.GetFaceAttribute(bytes, isExpression)
result.success(attr)
}
}
} }
fun bitmapToByteArray(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int): ByteArray? { fun bitmapToByteArray(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int): ByteArray? {
...@@ -150,6 +196,12 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler { ...@@ -150,6 +196,12 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler {
bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
) )
// val aspectRatio = rotatedBitmap.width.toDouble() / rotatedBitmap.height
// val width = 480
// val height = Math.round(width / aspectRatio).toInt()
//
// val final = Bitmap.createScaledBitmap(rotatedBitmap, width, height, false)
return rotatedBitmap return rotatedBitmap
} }
......
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:myhr_facescan/models/face_template.dart';
import 'package:myhr_facescan/enroll/template_model.dart'; import 'package:myhr_facescan/models/template.dart';
import 'package:myhr_facescan/luxand_controller.dart';
import 'package:myhr_facescan_example/main.dart'; import 'package:myhr_facescan_example/main.dart';
import 'package:myhr_facescan_example/template_controller.dart';
class FinishPage extends StatelessWidget { class FinishPage extends StatelessWidget {
const FinishPage({super.key, required this.templates}); const FinishPage({super.key, required this.templates});
...@@ -54,15 +52,10 @@ class Controller extends GetxController { ...@@ -54,15 +52,10 @@ class Controller extends GetxController {
final List<TemplateModel> templates; final List<TemplateModel> templates;
save() { save() {
var box = GetStorage(); var con = Get.find<TemplateController>();
List<FaceTemplate> faces = box.read('FACES') ?? [];
faces.add(FaceTemplate(
id: tConName.text,
eye: utf8.encode(templates[0].eye),
face: utf8.encode(templates[0].face)));
box.write('FACES', faces); con.add(FaceTemplate(
id: tConName.text, eye: templates[0].eye, face: templates[0].face));
Get.snackbar('Success', 'Face & Eye tempalte saved in device.'); Get.snackbar('Success', 'Face & Eye tempalte saved in device.');
Get.offAll(const HomePage()); Get.offAll(const HomePage());
......
...@@ -17,6 +17,14 @@ class EnrollPage extends StatelessWidget { ...@@ -17,6 +17,14 @@ class EnrollPage extends StatelessWidget {
child: EnrollFace( child: EnrollFace(
licenseKey: Env.luxandLicenseKey, licenseKey: Env.luxandLicenseKey,
onFinish: (templates) { onFinish: (templates) {
// var con = Get.find<TemplateController>();
// con.add(FaceTemplate(
// id: 'NaeNaee', eye: templates[0].eye, face: templates[0].face));
// Get.snackbar('Success', 'Face & Eye tempalte saved in device.');
// Get.offAll(const HomePage());
Get.off(LivenessPage(templates: templates)); Get.off(LivenessPage(templates: templates));
}, },
)), )),
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:myhr_facescan/enroll/template_model.dart'; import 'package:myhr_facescan/models/template.dart';
import 'package:myhr_facescan_example/enroll/finish.dart'; import 'package:myhr_facescan_example/enroll/finish.dart';
import 'package:myhr_facescan_example/env.dart'; import 'package:myhr_facescan_example/env.dart';
import 'package:myhr_facescan/active_liveness/index.dart'; import 'package:myhr_facescan/active_liveness/index.dart';
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:myhr_facescan_example/active_liveness/index.dart'; import 'package:myhr_facescan_example/active_liveness/index.dart';
import 'package:myhr_facescan_example/enroll/index.dart'; import 'package:myhr_facescan_example/enroll/index.dart';
import 'package:myhr_facescan_example/match_face/index.dart'; import 'package:myhr_facescan_example/match_face/index.dart';
import 'package:myhr_facescan_example/template_controller.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await GetStorage.init();
runApp(const MyApp()); runApp(const MyApp());
} }
...@@ -26,6 +25,8 @@ class HomePage extends StatelessWidget { ...@@ -26,6 +25,8 @@ class HomePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var con = Get.put(TemplateController());
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Luxand x Flutter'), title: const Text('Luxand x Flutter'),
...@@ -44,11 +45,15 @@ class HomePage extends StatelessWidget { ...@@ -44,11 +45,15 @@ class HomePage extends StatelessWidget {
child: const Text('Match Face')), child: const Text('Match Face')),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
var box = GetStorage(); con.removeAll();
await box.remove('FACES');
Get.snackbar('Success', 'Template in device is removed.'); Get.snackbar('Success', 'Template in device is removed.');
}, },
child: const Text('Clear storage')), child: const Text('Clear storage')),
ElevatedButton(
onPressed: () async {
Get.snackbar('Total: ${con.templates.length}', '');
},
child: const Text('Get total template')),
], ],
), ),
), ),
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get/get.dart';
import 'package:myhr_facescan/luxand_controller.dart';
import 'package:myhr_facescan/match_template/index.dart'; import 'package:myhr_facescan/match_template/index.dart';
import 'package:myhr_facescan_example/env.dart'; import 'package:myhr_facescan_example/env.dart';
import 'package:myhr_facescan_example/template_controller.dart';
class MatchFacePage extends StatelessWidget { class MatchFacePage extends StatelessWidget {
const MatchFacePage({super.key}); const MatchFacePage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var box = GetStorage(); var con = Get.find<TemplateController>();
var templates = box.read('FACES') as List<FaceTemplate>?;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
...@@ -19,7 +18,7 @@ class MatchFacePage extends StatelessWidget { ...@@ -19,7 +18,7 @@ class MatchFacePage extends StatelessWidget {
body: SafeArea( body: SafeArea(
child: MatchTemplateScanner( child: MatchTemplateScanner(
licenseKey: Env.luxandLicenseKey, licenseKey: Env.luxandLicenseKey,
templates: templates ?? [], templates: con.templates,
onFinish: (success, template) {}, onFinish: (success, template) {},
)), )),
); );
......
import 'package:get/get.dart';
import 'package:myhr_facescan/models/face_template.dart';
class TemplateController extends GetxController {
var templates = <FaceTemplate>[];
add(FaceTemplate data) {
templates.add(data);
}
removeAll() {
templates.clear();
}
}
...@@ -257,14 +257,6 @@ packages: ...@@ -257,14 +257,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
file: file:
dependency: transitive dependency: transitive
description: description:
...@@ -338,14 +330,6 @@ packages: ...@@ -338,14 +330,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.6.6" version: "4.6.6"
get_storage:
dependency: "direct main"
description:
name: get_storage
sha256: "39db1fffe779d0c22b3a744376e86febe4ade43bf65e06eab5af707dc84185a2"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
glob: glob:
dependency: transitive dependency: transitive
description: description:
...@@ -510,54 +494,6 @@ packages: ...@@ -510,54 +494,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.9.0"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
...@@ -787,22 +723,6 @@ packages: ...@@ -787,22 +723,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.3"
win32:
dependency: transitive
description:
name: win32
sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
url: "https://pub.dev"
source: hosted
version: "5.3.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "1.0.4"
xml: xml:
dependency: transitive dependency: transitive
description: description:
......
...@@ -18,7 +18,6 @@ dependencies: ...@@ -18,7 +18,6 @@ dependencies:
sdk: flutter sdk: flutter
get: get:
get_storage:
envied: ^0.5.3 envied: ^0.5.3
myhr_facescan: myhr_facescan:
# When depending on this package from a real application you should use: # When depending on this package from a real application you should use:
......
...@@ -6,7 +6,7 @@ import 'package:camera/camera.dart'; ...@@ -6,7 +6,7 @@ import 'package:camera/camera.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:myhr_facescan/active_liveness/enum.dart'; import 'package:myhr_facescan/active_liveness/enum.dart';
import 'package:myhr_facescan/active_liveness/face_gesture_verifier.dart'; import 'package:myhr_facescan/active_liveness/face_gesture_verifier.dart';
import 'package:myhr_facescan/enroll/template_model.dart'; import 'package:myhr_facescan/models/template.dart';
import 'package:myhr_facescan/luxand_controller.dart'; import 'package:myhr_facescan/luxand_controller.dart';
import 'package:myhr_facescan/utils/camera.dart'; import 'package:myhr_facescan/utils/camera.dart';
import 'package:myhr_facescan/utils/throttler.dart'; import 'package:myhr_facescan/utils/throttler.dart';
...@@ -121,7 +121,7 @@ class ActiveLivenessController extends GetxController { ...@@ -121,7 +121,7 @@ class ActiveLivenessController extends GetxController {
var isExpression = command == EFaceGesture.smile; var isExpression = command == EFaceGesture.smile;
var attr = await _luxand.getFaceAttribute(image, isExpression); var attr = await _luxand.getFaceAttribute(image, isExpression);
if (attr[0] == 0 && attr[1] == 0) { if (attr.isEmpty || (attr.isNotEmpty && attr[0] == 0 && attr[1] == 0)) {
await _handleFail(); await _handleFail();
return; return;
} }
......
...@@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; ...@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:myhr_facescan/active_liveness/controller.dart'; import 'package:myhr_facescan/active_liveness/controller.dart';
import 'package:myhr_facescan/camera_loading.dart'; import 'package:myhr_facescan/camera_loading.dart';
import 'package:myhr_facescan/enroll/template_model.dart'; import 'package:myhr_facescan/models/template.dart';
class ActiveLivenessVerifier extends StatelessWidget { class ActiveLivenessVerifier extends StatelessWidget {
const ActiveLivenessVerifier( const ActiveLivenessVerifier(
......
...@@ -2,8 +2,9 @@ import 'dart:io'; ...@@ -2,8 +2,9 @@ import 'dart:io';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:myhr_facescan/enroll/template_model.dart';
import 'package:myhr_facescan/luxand_controller.dart'; import 'package:myhr_facescan/luxand_controller.dart';
import 'package:myhr_facescan/models/face_position.dart';
import 'package:myhr_facescan/models/template.dart';
import 'package:myhr_facescan/utils/camera.dart'; import 'package:myhr_facescan/utils/camera.dart';
import 'package:myhr_facescan/utils/image.dart'; import 'package:myhr_facescan/utils/image.dart';
import 'package:myhr_facescan/utils/throttler.dart'; import 'package:myhr_facescan/utils/throttler.dart';
...@@ -33,9 +34,7 @@ class EnrollController extends GetxController { ...@@ -33,9 +34,7 @@ class EnrollController extends GetxController {
final int _imageStreamMilliSecond; final int _imageStreamMilliSecond;
var isFoundFace = false.obs; var isFoundFace = false.obs;
var faceX = 0.0.obs; var facePosition = FacePosition(x: 0, y: 0, width: 0).obs;
var faceY = 0.0.obs;
var faceW = 0.0.obs;
var isCameraReady = false.obs; var isCameraReady = false.obs;
var templates = <TemplateModel>[].obs; var templates = <TemplateModel>[].obs;
...@@ -95,6 +94,11 @@ class EnrollController extends GetxController { ...@@ -95,6 +94,11 @@ class EnrollController extends GetxController {
var template = await _luxand.getFaceTemplate(image); var template = await _luxand.getFaceTemplate(image);
var jpgImage = await convertYUV420toImageColor(image); var jpgImage = await convertYUV420toImageColor(image);
if (template.isEmpty) {
_gettingCurrentImage = false;
return;
}
templates.add(TemplateModel( templates.add(TemplateModel(
face: template[1], eye: template[0], image: jpgImage!)); face: template[1], eye: template[0], image: jpgImage!));
...@@ -112,14 +116,8 @@ class EnrollController extends GetxController { ...@@ -112,14 +116,8 @@ class EnrollController extends GetxController {
return; return;
} }
var isFound = data[0] > 0; isFoundFace.value = true;
isFoundFace.value = isFound; facePosition.value = data;
if (isFound) {
faceX.value = data[0].toDouble();
faceY.value = data[1].toDouble();
faceW.value = data[2].toDouble();
}
} }
}); });
} }
......
...@@ -4,7 +4,7 @@ import 'package:get/get.dart'; ...@@ -4,7 +4,7 @@ import 'package:get/get.dart';
import 'package:myhr_facescan/camera_loading.dart'; import 'package:myhr_facescan/camera_loading.dart';
import 'package:myhr_facescan/enroll/avatar.dart'; import 'package:myhr_facescan/enroll/avatar.dart';
import 'package:myhr_facescan/enroll/controller.dart'; import 'package:myhr_facescan/enroll/controller.dart';
import 'package:myhr_facescan/enroll/template_model.dart'; import 'package:myhr_facescan/models/template.dart';
import 'package:myhr_facescan/rectangle_paint.dart'; import 'package:myhr_facescan/rectangle_paint.dart';
class EnrollFace extends StatelessWidget { class EnrollFace extends StatelessWidget {
...@@ -45,14 +45,12 @@ class EnrollFace extends StatelessWidget { ...@@ -45,14 +45,12 @@ class EnrollFace extends StatelessWidget {
con.camController!.value.previewSize!.height, con.camController!.value.previewSize!.height,
con.camController!.value.previewSize!.width), con.camController!.value.previewSize!.width),
painter: RectanglePainter( painter: RectanglePainter(
xc: con.faceX.value, positions: [con.facePosition.value],
yc: con.faceY.value,
w: con.faceW.value,
paperWidth: con paperWidth: con
.camController!.value.previewSize!.height, .camController!.value.previewSize!.height,
paperHeight: paperHeight:
con.camController!.value.previewSize!.width, con.camController!.value.previewSize!.width,
topMessage: topMessage), topMessage: [topMessage]),
) )
: Container(), : Container(),
Positioned.fill( Positioned.fill(
......
...@@ -4,6 +4,9 @@ import 'dart:typed_data'; ...@@ -4,6 +4,9 @@ import 'dart:typed_data';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:myhr_facescan/models/face_match.dart';
import 'package:myhr_facescan/models/face_position.dart';
import 'package:myhr_facescan/models/face_template.dart';
class LuxandController extends GetxController { class LuxandController extends GetxController {
var platform = const MethodChannel("myhr_facescan"); var platform = const MethodChannel("myhr_facescan");
...@@ -31,10 +34,10 @@ class LuxandController extends GetxController { ...@@ -31,10 +34,10 @@ class LuxandController extends GetxController {
} }
freeResource() async { freeResource() async {
await platform.invokeMethod("freeResource"); await platform.invokeMethod<bool>("freeResource");
} }
Future<List<int>?> detectFace(CameraImage image) async { Future<FacePosition?> detectFace(CameraImage image) async {
List<int> strides = Int32List(image.planes.length * 2); List<int> strides = Int32List(image.planes.length * 2);
int index = 0; int index = 0;
final bytes = image.planes.map((plane) { final bytes = image.planes.map((plane) {
...@@ -52,10 +55,14 @@ class LuxandController extends GetxController { ...@@ -52,10 +55,14 @@ class LuxandController extends GetxController {
'strides': strides 'strides': strides
}); });
return result; if (result == null || result[0] <= 0) {
return null;
}
return FacePosition(x: result[0], y: result[1], width: result[2]);
} }
Future<List<String>> getFaceTemplate(CameraImage image) async { Future<List<Uint8List>> getFaceTemplate(CameraImage image) async {
List<int> strides = Int32List(image.planes.length * 2); List<int> strides = Int32List(image.planes.length * 2);
int index = 0; int index = 0;
final bytes = image.planes.map((plane) { final bytes = image.planes.map((plane) {
...@@ -66,16 +73,19 @@ class LuxandController extends GetxController { ...@@ -66,16 +73,19 @@ class LuxandController extends GetxController {
return plane.bytes; return plane.bytes;
}).toList(); }).toList();
var data = await platform.invokeMethod<List<Object?>>("getFaceTemplate", { var data =
await platform.invokeMethod<Map<Object?, Object?>>("getFaceTemplate", {
'platforms': bytes, 'platforms': bytes,
'height': image.height, 'height': image.height,
'width': image.width, 'width': image.width,
'strides': strides 'strides': strides
}); });
var result = [data![0].toString(), data[1].toString()]; if (data!['face'] == null) {
return [];
}
return result; return [data['eye'] as Uint8List, data['face'] as Uint8List];
} }
Future<List<double>> getFaceAttribute( Future<List<double>> getFaceAttribute(
...@@ -98,18 +108,27 @@ class LuxandController extends GetxController { ...@@ -98,18 +108,27 @@ class LuxandController extends GetxController {
'isExpression': isExpression 'isExpression': isExpression
}); });
return data!; if (data == null) {
return [];
}
return data;
} }
Future setAvailableTemplate(List<FaceTemplate> templates) async { Future setAvailableTemplate(List<FaceTemplate> templates) async {
await platform.invokeMethod("setAvailableTemplate", { var ids = templates.map((e) => e.id).toList();
'ids': templates.map((e) => e.id).toList(), var eyes = templates.map((e) => e.eye).toList();
'eyes': templates.map((e) => e.eye).toList(), var faces = templates.map((e) => e.face).toList();
'faces': templates.map((e) => e.face).toList(),
await platform.invokeMethod<bool>("setAvailableTemplate", {
'ids': ids,
'eyes': eyes,
'faces': faces,
}); });
} }
Future<String> detectFaceForMatch(CameraImage image) async { Future<List<FaceMatch>> detectFaceForMatch(
CameraImage image, List<FaceTemplate> templates) async {
List<int> strides = Int32List(image.planes.length * 2); List<int> strides = Int32List(image.planes.length * 2);
int index = 0; int index = 0;
final bytes = image.planes.map((plane) { final bytes = image.planes.map((plane) {
...@@ -120,21 +139,39 @@ class LuxandController extends GetxController { ...@@ -120,21 +139,39 @@ class LuxandController extends GetxController {
return plane.bytes; return plane.bytes;
}).toList(); }).toList();
var data = await platform.invokeMethod<String>("detectFaceForMatch", { var ids = templates.map((e) => e.id).toList();
var eyes = templates.map((e) => e.eye).toList();
var faces = templates.map((e) => e.face).toList();
var data =
await platform.invokeMethod<List<Object?>>("detectFaceForMatch", {
'platforms': bytes, 'platforms': bytes,
'height': image.height, 'height': image.height,
'width': image.width, 'width': image.width,
'strides': strides, 'strides': strides,
'ids': ids,
'eyes': eyes,
'faces': faces,
}); });
return data!; var result = <FaceMatch>[];
if (data == null) {
return result;
} }
}
class FaceTemplate { for (var d in data) {
final String id; var items = d.toString().split('(,)');
final Uint8List eye; result.add(FaceMatch(
final Uint8List face; id: items[0],
similarityEye: double.parse(items[1]),
similarityFace: double.parse(items[2]),
position: FacePosition(
x: int.parse(items[3]),
y: int.parse(items[4]),
width: int.parse(items[5]))));
}
FaceTemplate({required this.id, required this.eye, required this.face}); return result;
}
} }
...@@ -2,8 +2,10 @@ import 'dart:io'; ...@@ -2,8 +2,10 @@ import 'dart:io';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:myhr_facescan/enroll/template_model.dart';
import 'package:myhr_facescan/luxand_controller.dart'; import 'package:myhr_facescan/luxand_controller.dart';
import 'package:myhr_facescan/models/face_match.dart';
import 'package:myhr_facescan/models/face_template.dart';
import 'package:myhr_facescan/models/template.dart';
import 'package:myhr_facescan/utils/camera.dart'; import 'package:myhr_facescan/utils/camera.dart';
import 'package:myhr_facescan/utils/throttler.dart'; import 'package:myhr_facescan/utils/throttler.dart';
...@@ -33,6 +35,7 @@ class MatchTemplateController extends GetxController { ...@@ -33,6 +35,7 @@ class MatchTemplateController extends GetxController {
final List<FaceTemplate> _templates; final List<FaceTemplate> _templates;
var isCameraReady = false.obs; var isCameraReady = false.obs;
var faceMatchs = <FaceMatch>[].obs;
late LuxandController _luxand; late LuxandController _luxand;
...@@ -41,14 +44,13 @@ class MatchTemplateController extends GetxController { ...@@ -41,14 +44,13 @@ class MatchTemplateController extends GetxController {
super.onInit(); super.onInit();
await _luxand.initialize(); await _luxand.initialize();
// await _luxand.setAvailableTemplate(_templates);
await initCamera(); await initCamera();
camController!.initialize().then((_) async { camController!.initialize().then((_) {
isCameraReady.value = true; isCameraReady.value = true;
previewScale = getCameraPreviewScale(camController!.value.aspectRatio); previewScale = getCameraPreviewScale(camController!.value.aspectRatio);
await _luxand.setAvailableTemplate(_templates);
// Only open and close camera in iOS for low-tier device // Only open and close camera in iOS for low-tier device
if (Platform.isIOS) { if (Platform.isIOS) {
// _timer = Stream.periodic(const Duration(milliseconds: 500), (v) => v) // _timer = Stream.periodic(const Duration(milliseconds: 500), (v) => v)
...@@ -87,7 +89,9 @@ class MatchTemplateController extends GetxController { ...@@ -87,7 +89,9 @@ class MatchTemplateController extends GetxController {
_handleCameraAndroid(CameraImage image) async { _handleCameraAndroid(CameraImage image) async {
_throttler.run(() async { _throttler.run(() async {
var data = await _luxand.detectFaceForMatch(image); var data = await _luxand.detectFaceForMatch(image, _templates);
faceMatchs.value = data;
}); });
} }
} }
...@@ -2,9 +2,10 @@ import 'package:camera/camera.dart'; ...@@ -2,9 +2,10 @@ import 'package:camera/camera.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:myhr_facescan/camera_loading.dart'; import 'package:myhr_facescan/camera_loading.dart';
import 'package:myhr_facescan/enroll/template_model.dart';
import 'package:myhr_facescan/luxand_controller.dart';
import 'package:myhr_facescan/match_template/controller.dart'; import 'package:myhr_facescan/match_template/controller.dart';
import 'package:myhr_facescan/models/face_template.dart';
import 'package:myhr_facescan/models/template.dart';
import 'package:myhr_facescan/rectangle_paint.dart';
class MatchTemplateScanner extends StatelessWidget { class MatchTemplateScanner extends StatelessWidget {
const MatchTemplateScanner({ const MatchTemplateScanner({
...@@ -33,6 +34,19 @@ class MatchTemplateScanner extends StatelessWidget { ...@@ -33,6 +34,19 @@ class MatchTemplateScanner extends StatelessWidget {
scale: con.previewScale, scale: con.previewScale,
child: CameraPreview( child: CameraPreview(
con.camController!, con.camController!,
child: CustomPaint(
size: Size(con.camController!.value.previewSize!.height,
con.camController!.value.previewSize!.width),
painter: RectanglePainter(
positions: con.faceMatchs.map((d) => d.position).toList(),
paperWidth: con.camController!.value.previewSize!.height,
paperHeight: con.camController!.value.previewSize!.width,
topMessage: con.faceMatchs
.map((d) => '${d.id}, similarity=${d.similarityFace}')
.toList(),
rectColor: Colors.white,
rectStrokeWidth: 2),
),
), ),
) )
: const CameraLoading()); : const CameraLoading());
......
import 'package:myhr_facescan/models/face_position.dart';
class FaceMatch {
final String id;
final double similarityEye;
final double similarityFace;
final FacePosition position;
FaceMatch(
{required this.id,
required this.similarityEye,
required this.similarityFace,
required this.position});
}
class FacePosition {
final int x;
final int y;
final int width;
FacePosition({required this.x, required this.y, required this.width});
}
import 'dart:typed_data';
class FaceTemplate {
final String id;
final Uint8List? eye;
final Uint8List face;
FaceTemplate({required this.id, required this.eye, required this.face});
}
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class TemplateModel { class TemplateModel {
final String face; final Uint8List face;
final String eye; final Uint8List? eye;
final Image image; final Image image;
TemplateModel({required this.face, required this.eye, required this.image}); TemplateModel({required this.face, required this.eye, required this.image});
......
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:myhr_facescan/models/face_position.dart';
class RectanglePainter extends CustomPainter { class RectanglePainter extends CustomPainter {
final double xc; final List<FacePosition> positions;
final double yc;
final double w;
final double paperWidth; final double paperWidth;
final double paperHeight; final double paperHeight;
final String topMessage; final List<String> topMessage;
Color rectColor; Color rectColor;
double rectStrokeWidth; double rectStrokeWidth;
RectanglePainter( RectanglePainter(
{super.repaint, {super.repaint,
required this.xc, required this.positions,
required this.yc,
required this.w,
required this.paperWidth, required this.paperWidth,
required this.paperHeight, required this.paperHeight,
required this.topMessage, required this.topMessage,
...@@ -27,25 +24,27 @@ class RectanglePainter extends CustomPainter { ...@@ -27,25 +24,27 @@ class RectanglePainter extends CustomPainter {
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final xc = lerpDouble(0, size.width, this.xc / paperWidth)!;
final yc = lerpDouble(0, size.height, this.yc / paperHeight)!;
final width = lerpDouble(0, size.width, w / paperWidth)!;
var paint1 = Paint() var paint1 = Paint()
..color = rectColor ..color = rectColor
..style = PaintingStyle.stroke ..style = PaintingStyle.stroke
..strokeWidth = rectStrokeWidth; ..strokeWidth = rectStrokeWidth;
canvas.drawRect(
Rect.fromCenter(center: Offset(xc, yc), width: width, height: width),
paint1);
const textStyle = TextStyle( const textStyle = TextStyle(
color: Colors.green, color: Colors.green,
// fontSize: 30, // fontSize: 30,
); );
for (var pos in positions) {
final xc = lerpDouble(0, size.width, pos.x / paperWidth)!;
final yc = lerpDouble(0, size.height, pos.y / paperHeight)!;
final width = lerpDouble(0, size.width, pos.width / paperWidth)!;
canvas.drawRect(
Rect.fromCenter(center: Offset(xc, yc), width: width, height: width),
paint1);
var textSpan = TextSpan( var textSpan = TextSpan(
text: topMessage, text: topMessage[positions.indexOf(pos)],
style: textStyle, style: textStyle,
); );
final textPainter = TextPainter( final textPainter = TextPainter(
...@@ -60,7 +59,14 @@ class RectanglePainter extends CustomPainter { ...@@ -60,7 +59,14 @@ class RectanglePainter extends CustomPainter {
final yCenter = yc - (width / 2) - 20; final yCenter = yc - (width / 2) - 20;
textPainter.paint(canvas, Offset(xCenter, yCenter)); textPainter.paint(canvas, Offset(xCenter, yCenter));
} }
}
@override @override
bool shouldRepaint(CustomPainter oldDelegate) => true; bool shouldRepaint(CustomPainter oldDelegate) => true;
@override
bool? hitTest(Offset position) {
// TODO: implement hitTest
return super.hitTest(position);
}
} }
name: myhr_facescan name: myhr_facescan
description: Enroll face using luxand, verify real people by using active liveness detection description: Enroll face using luxand, verify real people by using active liveness detection
version: 0.1.0 version: 1.0.0
homepage: https://mygit.myhr.co.th/NaeNaee/myhr-facescan homepage: https://mygit.myhr.co.th/NaeNaee/myhr-facescan
environment: environment:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment