diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d4c3a57
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+/.idea/
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..675679c
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,58 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ namespace 'com.kbyai.faceattribute'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "com.kbyai.faceattribute"
+ minSdk 24
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ ndk {
+ abiFilters 'arm64-v8a', 'armeabi-v7a'
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'com.google.android.material:material:1.8.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ implementation 'androidx.preference:preference:1.2.0'
+ implementation 'androidx.preference:preference-ktx:1.2.0'
+
+ implementation "androidx.camera:camera-core:1.0.0-beta12"
+ implementation "androidx.camera:camera-camera2:1.0.0-beta12"
+ implementation "androidx.camera:camera-lifecycle:1.0.0-beta12"
+ implementation 'androidx.camera:camera-view:1.0.0-alpha19'
+
+ implementation project(path: ':libfacesdk')
+
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/kbyai/faceattribute/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/kbyai/faceattribute/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..b619f44
--- /dev/null
+++ b/app/src/androidTest/java/com/kbyai/faceattribute/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.kbyai.faceattribute
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.kbyai.faceattribute", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b805063
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/kbyai/faceattribute/AboutActivity.kt b/app/src/main/java/com/kbyai/faceattribute/AboutActivity.kt
new file mode 100644
index 0000000..2e731eb
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/AboutActivity.kt
@@ -0,0 +1,87 @@
+package com.kbyai.faceattribute
+
+import android.content.Intent
+import android.content.pm.ResolveInfo
+import android.net.Uri
+import android.os.Bundle
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+
+
+class AboutActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_about)
+
+ findViewById(R.id.txtMail).setOnClickListener {
+ val intent = Intent(Intent.ACTION_SEND)
+ intent.type = "plain/text"
+ intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("contact@kby-ai.com"))
+ intent.putExtra(Intent.EXTRA_SUBJECT, "License Request")
+ intent.putExtra(Intent.EXTRA_TEXT, "")
+ startActivity(Intent.createChooser(intent, ""))
+ }
+
+ findViewById(R.id.txtWhatsapp).setOnClickListener {
+ val general = Intent(Intent.ACTION_VIEW, Uri.parse("https://com.whatsapp/kbyai"))
+ val generalResolvers: HashSet = HashSet()
+ val generalResolveInfo: List = packageManager.queryIntentActivities(general, 0)
+ for (info in generalResolveInfo) {
+ if (info.activityInfo.packageName != null) {
+ generalResolvers.add(info.activityInfo.packageName)
+ }
+ }
+
+ val telegram = Intent(Intent.ACTION_VIEW, Uri.parse("https://wa.me/19092802609"))
+ var goodResolver = 0
+
+ val resInfo: List = packageManager.queryIntentActivities(telegram, 0)
+ if (!resInfo.isEmpty()) {
+ for (info in resInfo) {
+ if (info.activityInfo.packageName != null && !generalResolvers.contains(info.activityInfo.packageName)) {
+ goodResolver++
+ telegram.setPackage(info.activityInfo.packageName)
+ }
+ }
+ }
+
+ if (goodResolver != 1) {
+ telegram.setPackage(null)
+ }
+ if (telegram.resolveActivity(packageManager) != null) {
+ startActivity(telegram)
+ }
+ }
+
+ findViewById(R.id.txtTelegram).setOnClickListener {
+ val general = Intent(Intent.ACTION_VIEW, Uri.parse("https://t.com/kbyai"))
+ val generalResolvers: HashSet = HashSet()
+ val generalResolveInfo: List = packageManager.queryIntentActivities(general, 0)
+ for (info in generalResolveInfo) {
+ if (info.activityInfo.packageName != null) {
+ generalResolvers.add(info.activityInfo.packageName)
+ }
+ }
+
+ val telegram = Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/kbyai"))
+ var goodResolver = 0
+
+ val resInfo: List = packageManager.queryIntentActivities(telegram, 0)
+ if (!resInfo.isEmpty()) {
+ for (info in resInfo) {
+ if (info.activityInfo.packageName != null && !generalResolvers.contains(info.activityInfo.packageName)) {
+ goodResolver++
+ telegram.setPackage(info.activityInfo.packageName)
+ }
+ }
+ }
+
+ if (goodResolver != 1) {
+ telegram.setPackage(null)
+ }
+ if (telegram.resolveActivity(packageManager) != null) {
+ startActivity(telegram)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbyai/faceattribute/AttributeActivity.kt b/app/src/main/java/com/kbyai/faceattribute/AttributeActivity.kt
new file mode 100644
index 0000000..7cfc50d
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/AttributeActivity.kt
@@ -0,0 +1,80 @@
+package com.kbyai.faceattribute
+
+import android.graphics.Bitmap
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+
+class AttributeActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_attribute)
+
+ val faceImage = intent.getParcelableExtra("face_image") as? Bitmap
+ val livenessScore = intent.getFloatExtra("liveness", 0f)
+ val yaw = intent.getFloatExtra("yaw", 0f)
+ val roll = intent.getFloatExtra("roll", 0f)
+ val pitch = intent.getFloatExtra("pitch", 0f)
+ val face_quality = intent.getFloatExtra("face_quality", 0f)
+ val face_luminance = intent.getFloatExtra("face_luminance", 0f)
+ val left_eye_closed = intent.getFloatExtra("left_eye_closed", 0f)
+ val right_eye_closed = intent.getFloatExtra("right_eye_closed", 0f)
+ val face_occlusion = intent.getFloatExtra("face_occlusion", 0f)
+ val mouth_opened = intent.getFloatExtra("mouth_opened", 0f)
+ val age = intent.getIntExtra("age", 0)
+ val gender = intent.getIntExtra("gender", 0)
+
+ findViewById(R.id.imageFace).setImageBitmap(faceImage)
+
+ if (livenessScore > SettingsActivity.getLivenessThreshold(this)) {
+ val msg = String.format("Liveness: Real, score = %.03f", livenessScore)
+ findViewById(R.id.txtLiveness).text = msg
+ } else {
+ val msg = String.format("Liveness: Spoof, score = %.03f", livenessScore)
+ findViewById(R.id.txtLiveness).text = msg
+ }
+
+ if (face_quality < 0.5f) {
+ val msg = String.format("Quality: Low, score = %.03f", face_quality)
+ findViewById(R.id.txtQuality).text = msg
+ } else if(face_quality < 0.75f){
+ val msg = String.format("Quality: Medium, score = %.03f", face_quality)
+ findViewById(R.id.txtQuality).text = msg
+ } else {
+ val msg = String.format("Quality: High, score = %.03f", face_quality)
+ findViewById(R.id.txtQuality).text = msg
+ }
+
+ var msg = String.format("Luminance: %.03f", face_luminance)
+ findViewById(R.id.txtLuminance).text = msg
+
+ msg = String.format("Angles: yaw = %.03f, roll = %.03f, pitch = %.03f", yaw, roll, pitch)
+ findViewById(R.id.txtAngles).text = msg
+
+ if (face_occlusion > SettingsActivity.getOcclusionThreshold(this)) {
+ msg = String.format("Face occluded: score = %.03f", face_occlusion)
+ findViewById(R.id.txtOcclusion).text = msg
+ } else {
+ msg = String.format("Face not occluded: score = %.03f", face_occlusion)
+ findViewById(R.id.txtOcclusion).text = msg
+ }
+
+ msg = String.format("Left eye closed: %b, %.03f, Right eye closed: %b, %.03f", left_eye_closed > SettingsActivity.getEyecloseThreshold(this),
+ left_eye_closed, right_eye_closed > SettingsActivity.getEyecloseThreshold(this), right_eye_closed)
+ findViewById(R.id.txtEyeClosed).text = msg
+
+ msg = String.format("Mouth opened: %b, %.03f", mouth_opened > SettingsActivity.getMouthopenThreshold(this), mouth_opened)
+ findViewById(R.id.txtMouthOpened).text = msg
+
+ msg = String.format("Age: %d", age)
+ findViewById(R.id.txtAge).text = msg
+
+ if(gender == 0) {
+ msg = String.format("Gender: Male")
+ } else {
+ msg = String.format("Gender: Female")
+ }
+ findViewById(R.id.txtGender).text = msg
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbyai/faceattribute/CameraActivity.java b/app/src/main/java/com/kbyai/faceattribute/CameraActivity.java
new file mode 100644
index 0000000..d6f51d4
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/CameraActivity.java
@@ -0,0 +1,272 @@
+package com.kbyai.faceattribute;
+
+
+import static androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.media.Image;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.Size;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.camera.core.Camera;
+import androidx.camera.core.CameraSelector;
+import androidx.camera.core.ImageAnalysis;
+import androidx.camera.core.ImageProxy;
+import androidx.camera.core.Preview;
+import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.camera.view.PreviewView;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.kbyai.faceattribute.ResultActivity;
+import com.kbyai.faceattribute.SettingsActivity;
+import com.kbyai.facesdk.FaceBox;
+import com.kbyai.facesdk.FaceDetectionParam;
+import com.kbyai.facesdk.FaceSDK;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class CameraActivity extends AppCompatActivity {
+
+ static String TAG = CameraActivity.class.getSimpleName();
+ static int PREVIEW_WIDTH = 720;
+ static int PREVIEW_HEIGHT = 1280;
+
+ private ExecutorService cameraExecutorService;
+ private PreviewView viewFinder;
+ private Preview preview = null;
+ private ImageAnalysis imageAnalyzer = null;
+ private Camera camera = null;
+ private CameraSelector cameraSelector = null;
+ private ProcessCameraProvider cameraProvider = null;
+
+ private FaceView faceView;
+
+ private Context context;
+
+ private Boolean recognized = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_camera);
+
+ context = this;
+
+ viewFinder = findViewById(R.id.preview);
+ faceView = findViewById(R.id.faceView);
+ cameraExecutorService = Executors.newFixedThreadPool(1);
+
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_DENIED) {
+
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
+ } else {
+ viewFinder.post(() ->
+ {
+ setUpCamera();
+ });
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ recognized = false;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ faceView.setFaceBoxes(null);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ if(requestCode == 1) {
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_GRANTED) {
+
+ viewFinder.post(() ->
+ {
+ setUpCamera();
+ });
+ }
+ }
+ }
+
+ private void setUpCamera()
+ {
+ ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(CameraActivity.this);
+ cameraProviderFuture.addListener(() -> {
+
+ // CameraProvider
+ try {
+ cameraProvider = cameraProviderFuture.get();
+ } catch (ExecutionException e) {
+ } catch (InterruptedException e) {
+ }
+
+ // Build and bind the camera use cases
+ bindCameraUseCases();
+
+ }, ContextCompat.getMainExecutor(CameraActivity.this));
+ }
+
+ @SuppressLint({"RestrictedApi", "UnsafeExperimentalUsageError"})
+ private void bindCameraUseCases()
+ {
+ int rotation = viewFinder.getDisplay().getRotation();
+
+ cameraSelector = new CameraSelector.Builder().requireLensFacing(SettingsActivity.getCameraLens(this)).build();
+
+ preview = new Preview.Builder()
+ .setTargetResolution(new Size(PREVIEW_WIDTH, PREVIEW_HEIGHT))
+ .setTargetRotation(rotation)
+ .build();
+
+ imageAnalyzer = new ImageAnalysis.Builder()
+ .setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST)
+ .setTargetResolution(new Size(PREVIEW_WIDTH, PREVIEW_HEIGHT))
+ // Set initial target rotation, we will have to call this again if rotation changes
+ // during the lifecycle of this use case
+ .setTargetRotation(rotation)
+ .build();
+
+ imageAnalyzer.setAnalyzer(cameraExecutorService, new FaceAnalyzer());
+
+ cameraProvider.unbindAll();
+
+ try {
+ camera = cameraProvider.bindToLifecycle(
+ this, cameraSelector, preview, imageAnalyzer);
+
+ preview.setSurfaceProvider(viewFinder.getSurfaceProvider());
+ } catch (Exception exc) {
+ }
+ }
+
+ class FaceAnalyzer implements ImageAnalysis.Analyzer
+ {
+ @SuppressLint("UnsafeExperimentalUsageError")
+ @Override
+ public void analyze(@NonNull ImageProxy imageProxy)
+ {
+ analyzeImage(imageProxy);
+ }
+ }
+
+ @SuppressLint("UnsafeExperimentalUsageError")
+ private void analyzeImage(ImageProxy imageProxy)
+ {
+ if(recognized == true) {
+ imageProxy.close();
+ return;
+ }
+
+ try
+ {
+ Image image = imageProxy.getImage();
+
+ Image.Plane[] planes = image.getPlanes();
+ ByteBuffer yBuffer = planes[0].getBuffer();
+ ByteBuffer uBuffer = planes[1].getBuffer();
+ ByteBuffer vBuffer = planes[2].getBuffer();
+
+ int ySize = yBuffer.remaining();
+ int uSize = uBuffer.remaining();
+ int vSize = vBuffer.remaining();
+
+ byte[] nv21 = new byte[ySize + uSize + vSize];
+ yBuffer.get(nv21, 0, ySize);
+ vBuffer.get(nv21, ySize, vSize);
+ uBuffer.get(nv21, ySize + vSize, uSize);
+
+ Bitmap bitmap = FaceSDK.yuv2Bitmap(nv21, image.getWidth(), image.getHeight(), 7);
+
+ FaceDetectionParam param = new FaceDetectionParam();
+ param.check_liveness = true;
+
+ List faceBoxes = FaceSDK.faceDetection(bitmap, param);
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ faceView.setFrameSize(new Size(bitmap.getWidth(), bitmap.getHeight()));
+ faceView.setFaceBoxes(faceBoxes);
+ }
+ });
+
+ if(faceBoxes.size() > 0) {
+ FaceBox faceBox = faceBoxes.get(0);
+ if(faceBox.liveness > SettingsActivity.getLivenessThreshold(context)) {
+ byte[] templates = FaceSDK.templateExtraction(bitmap, faceBox);
+
+ float maxSimiarlity = 0;
+ Person maximiarlityPerson = null;
+ for(Person person : DBManager.personList) {
+ float similarity = FaceSDK.similarityCalculation(templates, person.templates);
+ if(similarity > maxSimiarlity) {
+ maxSimiarlity = similarity;
+ maximiarlityPerson = person;
+ }
+ }
+
+ if(maxSimiarlity > SettingsActivity.getIdentifyThreshold(this)) {
+ recognized = true;
+ final Person identifiedPerson = maximiarlityPerson;
+ final float identifiedSimilarity = maxSimiarlity;
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Bitmap faceImage = Utils.cropFace(bitmap, faceBox);
+
+ Intent intent = new Intent(context, ResultActivity.class);
+ intent.putExtra("identified_face", faceImage);
+ intent.putExtra("enrolled_face", identifiedPerson.face);
+ intent.putExtra("identified_name", identifiedPerson.name);
+ intent.putExtra("similarity", identifiedSimilarity);
+ intent.putExtra("liveness", faceBox.liveness);
+ intent.putExtra("yaw", faceBox.yaw);
+ intent.putExtra("roll", faceBox.roll);
+ intent.putExtra("pitch", faceBox.pitch);
+ intent.putExtra("face_quality", faceBox.face_quality);
+ intent.putExtra("face_luminance", faceBox.face_luminance);
+
+ startActivity(intent);
+ }
+ });
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ imageProxy.close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbyai/faceattribute/CaptureActivity.java b/app/src/main/java/com/kbyai/faceattribute/CaptureActivity.java
new file mode 100644
index 0000000..c6e00a7
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/CaptureActivity.java
@@ -0,0 +1,423 @@
+package com.kbyai.faceattribute;
+
+
+import static androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+import android.media.Image;
+import android.os.Bundle;
+import android.text.Layout;
+import android.util.Log;
+import android.util.Size;
+import android.view.View;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.camera.core.Camera;
+import androidx.camera.core.CameraSelector;
+import androidx.camera.core.ImageAnalysis;
+import androidx.camera.core.ImageProxy;
+import androidx.camera.core.Preview;
+import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.camera.view.PreviewView;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.kbyai.facesdk.FaceBox;
+import com.kbyai.facesdk.FaceDetectionParam;
+import com.kbyai.facesdk.FaceSDK;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+
+
+public class CaptureActivity extends AppCompatActivity implements CaptureView.ViewModeChanged{
+
+ static String TAG = CaptureActivity.class.getSimpleName();
+ static int PREVIEW_WIDTH = 720;
+ static int PREVIEW_HEIGHT = 1280;
+
+ private ExecutorService cameraExecutorService;
+ private PreviewView viewFinder;
+ private Preview preview = null;
+ private ImageAnalysis imageAnalyzer = null;
+ private Camera camera = null;
+ private CameraSelector cameraSelector = null;
+ private ProcessCameraProvider cameraProvider = null;
+
+ private CaptureView captureView;
+
+ private TextView warningTxt;
+
+ private TextView livenessTxt;
+
+ private TextView qualityTxt;
+
+ private TextView luminaceTxt;
+
+ private ConstraintLayout lytCaptureResult;
+
+ private Context context;
+
+ private Bitmap capturedBitmap = null;
+
+ private FaceBox capturedFace = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_capture);
+
+ context = this;
+
+ viewFinder = findViewById(R.id.preview);
+ captureView = findViewById(R.id.captureView);
+ warningTxt = findViewById(R.id.txtWarning);
+ livenessTxt = findViewById(R.id.txtLiveness);
+ qualityTxt = findViewById(R.id.txtQuality);
+ luminaceTxt = findViewById(R.id.txtLuminance);
+ lytCaptureResult = findViewById(R.id.lytCaptureResult);
+ cameraExecutorService = Executors.newFixedThreadPool(1);
+
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_DENIED) {
+
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
+ } else {
+ viewFinder.post(() ->
+ {
+ setUpCamera();
+ });
+ }
+
+ captureView.setViewModeInterface(this);
+ captureView.setViewMode(CaptureView.VIEW_MODE.NO_FACE_PREPARE);
+
+ findViewById(R.id.buttonEnroll).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Bitmap faceImage = Utils.cropFace(capturedBitmap, capturedFace);
+ byte[] templates = FaceSDK.templateExtraction(capturedBitmap, capturedFace);
+
+ DBManager dbManager = new DBManager(context);
+ final int min = 10000;
+ final int max = 20000;
+ final int random = new Random().nextInt((max - min) + 1) + min;
+
+ dbManager.insertPerson("Person" + random, faceImage, templates);
+ Toast.makeText(context, getString(R.string.person_enrolled), Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ });
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ captureView.setFaceBoxes(null);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ if(requestCode == 1) {
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_GRANTED) {
+
+ viewFinder.post(() ->
+ {
+ setUpCamera();
+ });
+ }
+ }
+ }
+
+ private void setUpCamera()
+ {
+ ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(CaptureActivity.this);
+ cameraProviderFuture.addListener(() -> {
+
+ // CameraProvider
+ try {
+ cameraProvider = cameraProviderFuture.get();
+ } catch (ExecutionException e) {
+ } catch (InterruptedException e) {
+ }
+
+ // Build and bind the camera use cases
+ bindCameraUseCases();
+
+ }, ContextCompat.getMainExecutor(CaptureActivity.this));
+ }
+
+ @SuppressLint({"RestrictedApi", "UnsafeExperimentalUsageError"})
+ private void bindCameraUseCases()
+ {
+ int rotation = viewFinder.getDisplay().getRotation();
+
+ cameraSelector = new CameraSelector.Builder().requireLensFacing(SettingsActivity.getCameraLens(this)).build();
+
+ preview = new Preview.Builder()
+ .setTargetResolution(new Size(PREVIEW_WIDTH, PREVIEW_HEIGHT))
+ .setTargetRotation(rotation)
+ .build();
+
+ imageAnalyzer = new ImageAnalysis.Builder()
+ .setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST)
+ .setTargetResolution(new Size(PREVIEW_WIDTH, PREVIEW_HEIGHT))
+ // Set initial target rotation, we will have to call this again if rotation changes
+ // during the lifecycle of this use case
+ .setTargetRotation(rotation)
+ .build();
+
+ imageAnalyzer.setAnalyzer(cameraExecutorService, new FaceAnalyzer());
+
+ cameraProvider.unbindAll();
+
+ try {
+ camera = cameraProvider.bindToLifecycle(
+ this, cameraSelector, preview, imageAnalyzer);
+
+ preview.setSurfaceProvider(viewFinder.getSurfaceProvider());
+ } catch (Exception exc) {
+ }
+ }
+
+ @Override
+ public void view5_finished() {
+
+ FaceDetectionParam param = new FaceDetectionParam();
+ param.check_liveness = true;
+
+ List faceBoxes = FaceSDK.faceDetection(capturedBitmap, param);
+ if(faceBoxes != null && faceBoxes.size() > 0) {
+ if(faceBoxes.get(0).liveness > SettingsActivity.getLivenessThreshold(context)) {
+ String msg = String.format("Liveness: Real, score = %.03f", faceBoxes.get(0).liveness);
+ livenessTxt.setText(msg);
+ }
+ else {
+ String msg = String.format("Liveness: Spoof, score = %.03f", faceBoxes.get(0).liveness);
+ livenessTxt.setText(msg);
+ }
+ }
+
+ if(capturedFace.face_quality < 0.5f) {
+ String msg = String.format("Quality: Low, score = %.03f", capturedFace.face_quality);
+ qualityTxt.setText(msg);
+ } else if(capturedFace.face_quality < 0.75f) {
+ String msg = String.format("Quality: Medium, score = %.03f", capturedFace.face_quality);
+ qualityTxt.setText(msg);
+ } else {
+ String msg = String.format("Quality: High, score = %.03f", capturedFace.face_quality);
+ qualityTxt.setText(msg);
+ }
+
+ String msg = String.format("Luminance: %.03f", capturedFace.face_luminance);
+ luminaceTxt.setText(msg);
+
+ lytCaptureResult.setVisibility(View.VISIBLE);
+ }
+
+ class FaceAnalyzer implements ImageAnalysis.Analyzer
+ {
+ @SuppressLint("UnsafeExperimentalUsageError")
+ @Override
+ public void analyze(@NonNull ImageProxy imageProxy)
+ {
+ analyzeImage(imageProxy);
+ }
+ }
+
+ @SuppressLint("UnsafeExperimentalUsageError")
+ private void analyzeImage(ImageProxy imageProxy)
+ {
+ if(captureView.viewMode == CaptureView.VIEW_MODE.NO_FACE_PREPARE) {
+ imageProxy.close();
+ return;
+ }
+
+ try
+ {
+ Image image = imageProxy.getImage();
+
+ Image.Plane[] planes = image.getPlanes();
+ ByteBuffer yBuffer = planes[0].getBuffer();
+ ByteBuffer uBuffer = planes[1].getBuffer();
+ ByteBuffer vBuffer = planes[2].getBuffer();
+
+ int ySize = yBuffer.remaining();
+ int uSize = uBuffer.remaining();
+ int vSize = vBuffer.remaining();
+
+ byte[] nv21 = new byte[ySize + uSize + vSize];
+ yBuffer.get(nv21, 0, ySize);
+ vBuffer.get(nv21, ySize, vSize);
+ uBuffer.get(nv21, ySize + vSize, uSize);
+
+ Bitmap bitmap = FaceSDK.yuv2Bitmap(nv21, image.getWidth(), image.getHeight(), 7);
+
+ FaceDetectionParam param = new FaceDetectionParam();
+ param.check_face_occlusion = true;
+ param.check_eye_closeness = true;
+ param.check_mouth_opened = true;
+
+ List faceBoxes = FaceSDK.faceDetection(bitmap, param);
+ FACE_CAPTURE_STATE faceCaptureState = checkFace(faceBoxes, this);
+
+ if(captureView.viewMode == CaptureView.VIEW_MODE.REPEAT_NO_FACE_PREPARE) {
+ if(faceCaptureState.compareTo(FACE_CAPTURE_STATE.NO_FACE) > 0) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ captureView.setViewMode(CaptureView.VIEW_MODE.TO_FACE_CIRCLE);
+ }
+ });
+ }
+ } else if(captureView.viewMode == CaptureView.VIEW_MODE.FACE_CIRCLE) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ captureView.setFrameSize(new Size(bitmap.getWidth(), bitmap.getHeight()));
+ captureView.setFaceBoxes(faceBoxes);
+
+ if(faceCaptureState == FACE_CAPTURE_STATE.NO_FACE) {
+ warningTxt.setText("");
+
+ captureView.setViewMode(CaptureView.VIEW_MODE.FACE_CIRCLE_TO_NO_FACE);
+ }
+ else if(faceCaptureState == FACE_CAPTURE_STATE.MULTIPLE_FACES)
+ warningTxt.setText("Multiple face detected!");
+ else if(faceCaptureState == FACE_CAPTURE_STATE.FIT_IN_CIRCLE)
+ warningTxt.setText("Fit in circle!");
+ else if(faceCaptureState == FACE_CAPTURE_STATE.MOVE_CLOSER)
+ warningTxt.setText("Move closer!");
+ else if(faceCaptureState == FACE_CAPTURE_STATE.NO_FRONT)
+ warningTxt.setText("Not fronted face!");
+ else if(faceCaptureState == FACE_CAPTURE_STATE.FACE_OCCLUDED)
+ warningTxt.setText("Face occluded!");
+ else if(faceCaptureState == FACE_CAPTURE_STATE.EYE_CLOSED)
+ warningTxt.setText("Eye closed!");
+ else if(faceCaptureState == FACE_CAPTURE_STATE.MOUTH_OPENED)
+ warningTxt.setText("Mouth opened!");
+ else if(faceCaptureState == FACE_CAPTURE_STATE.SPOOFED_FACE)
+ warningTxt.setText("Spoof face");
+ else {
+ warningTxt.setText("");
+ captureView.setViewMode(CaptureView.VIEW_MODE.FACE_CAPTURE_PREPARE);
+
+ capturedBitmap = bitmap;
+ capturedFace = faceBoxes.get(0);
+ captureView.setCapturedBitmap(capturedBitmap);
+ }
+ }
+ });
+ } else if(captureView.viewMode == CaptureView.VIEW_MODE.FACE_CAPTURE_PREPARE) {
+ if(faceCaptureState == FACE_CAPTURE_STATE.CAPTURE_OK) {
+ if(faceBoxes.get(0).face_quality > capturedFace.face_quality) {
+ capturedBitmap = bitmap;
+ capturedFace = faceBoxes.get(0);
+ captureView.setCapturedBitmap(capturedBitmap);
+ }
+ }
+ } else if(captureView.viewMode == CaptureView.VIEW_MODE.FACE_CAPTURE_DONE) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ cameraProvider.unbindAll();
+ }
+ });
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ imageProxy.close();
+ }
+ }
+
+ public static FACE_CAPTURE_STATE checkFace(List faceBoxes, Context context) {
+ if(faceBoxes == null || faceBoxes.size() == 0)
+ return FACE_CAPTURE_STATE.NO_FACE;
+
+ if(faceBoxes.size() > 1) {
+ return FACE_CAPTURE_STATE.MULTIPLE_FACES;
+ }
+
+ FaceBox faceBox = faceBoxes.get(0);
+ float faceLeft = Float.MAX_VALUE;
+ float faceRight = 0f;
+ float faceBottom = 0f;
+ for(int i = 0; i < 68; i ++) {
+ faceLeft = Math.min(faceLeft, faceBox.landmarks_68[i * 2]);
+ faceRight = Math.max(faceRight, faceBox.landmarks_68[i * 2]);
+ faceBottom = Math.max(faceBottom, faceBox.landmarks_68[i * 2 + 1]);
+ }
+
+ float sizeRate = 0.30f;
+ float interRate = 0.03f;
+ Size frameSize = new Size(PREVIEW_WIDTH, PREVIEW_HEIGHT);
+ RectF roiRect = CaptureView.getROIRect(frameSize);
+ float centerY = (faceBox.y2 + faceBox.y1) / 2;
+ float topY = centerY - (faceBox.y2 - faceBox.y1) * 2 / 3;
+ float interX = Math.max(0f, roiRect.left - faceLeft) + Math.max(0f, faceRight - roiRect.right);
+ float interY = Math.max(0f, roiRect.top - topY) + Math.max(0f, faceBottom - roiRect.bottom);
+ if(interX / roiRect.width() > interRate || interY / roiRect.height() > interRate) {
+ return FACE_CAPTURE_STATE.FIT_IN_CIRCLE;
+ }
+
+ if(interX / roiRect.width() > interRate || interY / roiRect.height() > interRate) {
+ return FACE_CAPTURE_STATE.FIT_IN_CIRCLE;
+ }
+
+ if((faceBox.y2 - faceBox.y1) * (faceBox.x2 - faceBox.x1) < roiRect.width() * roiRect.height() * sizeRate) {
+ return FACE_CAPTURE_STATE.MOVE_CLOSER;
+ }
+
+ if(Math.abs(faceBox.yaw) > SettingsActivity.getYawThreshold(context) ||
+ Math.abs(faceBox.roll) > SettingsActivity.getRollThreshold(context) ||
+ Math.abs(faceBox.pitch) > SettingsActivity.getPitchThreshold(context)) {
+ return FACE_CAPTURE_STATE.NO_FRONT;
+ }
+
+ if(faceBox.face_occlusion > SettingsActivity.getOcclusionThreshold(context)) {
+ return FACE_CAPTURE_STATE.FACE_OCCLUDED;
+ }
+
+ if(faceBox.left_eye_closed > SettingsActivity.getEyecloseThreshold(context) ||
+ faceBox.right_eye_closed > SettingsActivity.getEyecloseThreshold(context)) {
+ return FACE_CAPTURE_STATE.EYE_CLOSED;
+ }
+
+ if(faceBox.mouth_opened > SettingsActivity.getMouthopenThreshold(context)) {
+ return FACE_CAPTURE_STATE.MOUTH_OPENED;
+ }
+
+ return FACE_CAPTURE_STATE.CAPTURE_OK;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbyai/faceattribute/CaptureView.java b/app/src/main/java/com/kbyai/faceattribute/CaptureView.java
new file mode 100644
index 0000000..db140e8
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/CaptureView.java
@@ -0,0 +1,571 @@
+package com.kbyai.faceattribute;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Size;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import com.kbyai.facesdk.FaceBox;
+
+import java.util.List;
+
+public class CaptureView extends View implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
+
+ enum VIEW_MODE {
+ MODE_NONE,
+ NO_FACE_PREPARE,
+ REPEAT_NO_FACE_PREPARE,
+ TO_FACE_CIRCLE,
+ FACE_CIRCLE_TO_NO_FACE,
+ FACE_CIRCLE,
+ FACE_CAPTURE_PREPARE,
+ FACE_CAPTURE_DONE,
+ }
+
+ private Context context;
+
+ private Paint scrimPaint;
+
+ private Paint eraserPaint;
+
+ private Paint outSideRoundPaint;
+
+ private Paint outSideRoundNonPaint;
+
+ private Paint outSideActiveRoundPaint;
+
+ private Paint outSideRoundNonFacePaint;
+
+ private Paint outSideRoundFacePaint;
+
+ private Paint outSideRoundActiveFacePaint;
+ private boolean scrimInited;
+ private Size frameSize = new Size(720, 1280);
+
+ private List faceBoxes;
+
+ private float animateValue;
+ private ValueAnimator valueAnimator;
+
+ public VIEW_MODE viewMode = VIEW_MODE.MODE_NONE;
+
+ private int repeatCount = 0;
+
+ private ViewModeChanged viewModeInterface;
+
+ private Bitmap capturedBitmap;
+
+ private Bitmap roiBitmap;
+
+ interface ViewModeChanged
+ {
+ public void view5_finished();
+ }
+
+ public void setViewModeInterface(ViewModeChanged viewMode) {
+ viewModeInterface = viewMode;
+ }
+
+ public CaptureView(Context context) {
+ this(context, null);
+
+ this.context = context;
+ init();
+ }
+
+ public CaptureView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ this.context = context;
+
+ init();
+ }
+
+ public void init() {
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
+ scrimPaint = new Paint();
+
+ eraserPaint = new Paint();
+ eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+
+ outSideRoundPaint = new Paint();
+ outSideRoundPaint.setStyle(Paint.Style.STROKE);
+ outSideRoundPaint.setStrokeWidth(7);
+ outSideRoundPaint.setColor(ContextCompat.getColor(context, R.color.md_theme_dark_errorContainer));
+ outSideRoundPaint.setAntiAlias(true);
+
+ outSideRoundNonPaint = new Paint();
+ outSideRoundNonPaint.setStyle(Paint.Style.STROKE);
+ outSideRoundNonPaint.setStrokeWidth(2);
+ outSideRoundNonPaint.setColor(ContextCompat.getColor(context, R.color.md_theme_dark_inverseOnSurface));
+ outSideRoundNonPaint.setAntiAlias(true);
+
+ outSideActiveRoundPaint = new Paint();
+ outSideActiveRoundPaint.setStyle(Paint.Style.STROKE);
+ outSideActiveRoundPaint.setStrokeWidth(8);
+ outSideActiveRoundPaint.setColor(ContextCompat.getColor(context, R.color.md_theme_dark_onSurface));
+ outSideActiveRoundPaint.setAntiAlias(true);
+
+ outSideRoundNonFacePaint = new Paint();
+ outSideRoundNonFacePaint.setStyle(Paint.Style.STROKE);
+ outSideRoundNonFacePaint.setStrokeWidth(10);
+ outSideRoundNonFacePaint.setColor(ContextCompat.getColor(context, R.color.md_theme_dark_inverseOnSurface));
+ outSideRoundNonFacePaint.setAntiAlias(true);
+
+ outSideRoundFacePaint = new Paint();
+ outSideRoundFacePaint.setStyle(Paint.Style.STROKE);
+ outSideRoundFacePaint.setStrokeWidth(10);
+ outSideRoundFacePaint.setColor(ContextCompat.getColor(context, R.color.md_theme_dark_errorContainer));
+ outSideRoundFacePaint.setAntiAlias(true);
+
+ outSideRoundActiveFacePaint = new Paint();
+ outSideRoundActiveFacePaint.setStyle(Paint.Style.STROKE);
+ outSideRoundActiveFacePaint.setStrokeWidth(10);
+ outSideRoundActiveFacePaint.setColor(ContextCompat.getColor(context, R.color.md_theme_dark_onPrimaryContainer));
+ outSideRoundActiveFacePaint.setAntiAlias(true);
+ }
+
+ public void setFrameSize(Size frameSize)
+ {
+ this.frameSize = frameSize;
+ }
+
+ public void setFaceBoxes(List faceBoxes)
+ {
+ this.faceBoxes = faceBoxes;
+ invalidate();
+ }
+
+ public void setCapturedBitmap(Bitmap bitmap) {
+ capturedBitmap = bitmap;
+
+ RectF roiRect = CaptureView.getROIRect1(frameSize);
+
+ float ratioView = getWidth() / (float)getHeight();
+ float ratioFrame = frameSize.getWidth() / (float)frameSize.getHeight();
+ RectF roiViewRect = new RectF();
+
+ if(ratioView < ratioFrame) {
+ float dx = ((getHeight() * ratioFrame) - getWidth()) / 2;
+ float dy = 0f;
+ float ratio = getHeight() / (float)frameSize.getHeight();
+
+ float x1 = roiRect.left * ratio - dx;
+ float y1 = roiRect.top * ratio - dy;
+ float x2 = roiRect.right * ratio - dx;
+ float y2 = roiRect.bottom * ratio - dy;
+
+ roiViewRect = new RectF(x1, y1, x2, y2);
+ } else {
+ float dx = 0;
+ float dy = ((getWidth() / ratioFrame) - getHeight()) / 2;
+ float ratio = getHeight() / (float)frameSize.getHeight();
+
+ float x1 = roiRect.left * ratio - dx;
+ float y1 = roiRect.top * ratio - dy;
+ float x2 = roiRect.right * ratio - dx;
+ float y2 = roiRect.bottom * ratio - dy;
+
+ roiViewRect = new RectF(x1, y1, x2, y2);
+ }
+
+ Rect roiRectSrc = new Rect();
+ Rect roiViewRectSrc = new Rect();
+ roiRect.round(roiRectSrc);
+ roiViewRect.round(roiViewRectSrc);
+ roiBitmap = Bitmap.createBitmap(roiRectSrc.width(), roiRectSrc.height(), Bitmap.Config.ARGB_8888);
+
+ final Path path = new Path();
+ path.addCircle(
+ (float) (roiRectSrc.width() / 2)
+ , (float) (roiRectSrc.height() / 2)
+ , (float) Math.min(roiRectSrc.width(), (roiRectSrc.height() / 2))
+ , Path.Direction.CCW
+ );
+
+ final Canvas canvas1 = new Canvas(roiBitmap);
+ canvas1.clipPath(path);
+ canvas1.drawBitmap(capturedBitmap, roiRectSrc, new Rect(0, 0, roiRectSrc.width(), roiRectSrc.height()), null);
+ }
+
+ public void setViewMode(VIEW_MODE mode) {
+ this.viewMode = mode;
+
+ if(valueAnimator != null) {
+ valueAnimator.pause();
+ }
+
+ if(this.viewMode == VIEW_MODE.NO_FACE_PREPARE) {
+ ValueAnimator animator = ValueAnimator.ofFloat(1.4f, 0.88f);
+ animator.addUpdateListener(this);
+ animator.addListener(this);
+ animator.setDuration(800);
+
+ valueAnimator = animator;
+ } else if(viewMode == VIEW_MODE.REPEAT_NO_FACE_PREPARE) {
+ ValueAnimator animator = ValueAnimator.ofFloat(0.88f, 0.92f);
+ animator.addUpdateListener(this);
+ animator.addListener(this);
+ animator.setRepeatMode(ValueAnimator.REVERSE);
+ animator.setRepeatCount(-1);
+ animator.setDuration(1300);
+
+ valueAnimator = animator;
+ } else if(viewMode == VIEW_MODE.TO_FACE_CIRCLE) {
+ ValueAnimator animator = ValueAnimator.ofFloat(1.4f, 0.0f);
+ animator.addUpdateListener(this);
+ animator.addListener(this);
+ animator.setDuration(800);
+
+ valueAnimator = animator;
+ } else if(viewMode == VIEW_MODE.FACE_CIRCLE_TO_NO_FACE) {
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1.0f);
+ animator.addUpdateListener(this);
+ animator.addListener(this);
+ animator.setDuration(600);
+
+ valueAnimator = animator;
+ } else if(viewMode == VIEW_MODE.FACE_CIRCLE) {
+ invalidate();
+ return;
+ } else if(viewMode == VIEW_MODE.FACE_CAPTURE_PREPARE) {
+ ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ animator.addUpdateListener(this);
+ animator.addListener(this);
+ animator.setDuration(500);
+
+ valueAnimator = animator;
+ } else if(viewMode == VIEW_MODE.FACE_CAPTURE_DONE) {
+ ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ animator.addUpdateListener(this);
+ animator.addListener(this);
+ animator.setDuration(500);
+ }
+
+ valueAnimator.start();
+ }
+
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator valueAnimator) {
+ float value = (float)valueAnimator.getAnimatedValue();
+ animateValue = value;
+ invalidate();
+ }
+
+ @Override
+ public void onAnimationStart(@NonNull Animator animator) {
+ repeatCount = 0;
+ }
+
+ @Override
+ public void onAnimationEnd(@NonNull Animator animator) {
+ if(viewMode == VIEW_MODE.NO_FACE_PREPARE) {
+ setViewMode(VIEW_MODE.REPEAT_NO_FACE_PREPARE);
+ } else if(viewMode == VIEW_MODE.TO_FACE_CIRCLE) {
+ setViewMode(VIEW_MODE.FACE_CIRCLE);
+ } else if(viewMode == VIEW_MODE.FACE_CIRCLE_TO_NO_FACE) {
+ setViewMode(VIEW_MODE.NO_FACE_PREPARE);
+ } else if(viewMode == VIEW_MODE.FACE_CAPTURE_PREPARE) {
+ setViewMode(VIEW_MODE.FACE_CAPTURE_DONE);
+ } else if(viewMode == VIEW_MODE.FACE_CAPTURE_DONE) {
+ if(viewModeInterface != null) {
+ viewModeInterface.view5_finished();
+ }
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(@NonNull Animator animator) {
+ }
+
+ @Override
+ public void onAnimationRepeat(@NonNull Animator animator) {
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if(scrimInited == false) {
+ scrimInited = true;
+ scrimPaint.setShader(
+ new LinearGradient(
+ 0,
+ 0,
+ canvas.getWidth(),
+ canvas.getHeight(),
+ context.getColor(R.color.md_theme_dark_surface),
+ context.getColor(R.color.md_theme_dark_scrim),
+ Shader.TileMode.CLAMP));
+ }
+
+ if(viewMode == VIEW_MODE.FACE_CIRCLE ||
+ viewMode == VIEW_MODE.FACE_CAPTURE_PREPARE ||
+ viewMode == VIEW_MODE.FACE_CAPTURE_DONE ||
+ (viewMode == VIEW_MODE.TO_FACE_CIRCLE && animateValue < 1.0f) ||
+ viewMode == VIEW_MODE.FACE_CIRCLE_TO_NO_FACE) {
+
+ if(viewMode == VIEW_MODE.FACE_CIRCLE_TO_NO_FACE) {
+ scrimPaint.setAlpha((int)((1 - animateValue) * 255));
+ } else {
+ scrimPaint.setAlpha(255);
+ }
+ canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), scrimPaint);
+ }
+
+ RectF roiRect = CaptureView.getROIRect1(frameSize);
+
+ float ratioView = canvas.getWidth() / (float)canvas.getHeight();
+ float ratioFrame = frameSize.getWidth() / (float)frameSize.getHeight();
+ RectF roiViewRect = new RectF();
+
+ if(ratioView < ratioFrame) {
+ float dx = ((canvas.getHeight() * ratioFrame) - canvas.getWidth()) / 2;
+ float dy = 0f;
+ float ratio = canvas.getHeight() / (float)frameSize.getHeight();
+
+ float x1 = roiRect.left * ratio - dx;
+ float y1 = roiRect.top * ratio - dy;
+ float x2 = roiRect.right * ratio - dx;
+ float y2 = roiRect.bottom * ratio - dy;
+
+ roiViewRect = new RectF(x1, y1, x2, y2);
+ } else {
+ float dx = 0;
+ float dy = ((canvas.getWidth() / ratioFrame) - canvas.getHeight()) / 2;
+ float ratio = canvas.getHeight() / (float)frameSize.getHeight();
+
+ float x1 = roiRect.left * ratio - dx;
+ float y1 = roiRect.top * ratio - dy;
+ float x2 = roiRect.right * ratio - dx;
+ float y2 = roiRect.bottom * ratio - dy;
+
+ roiViewRect = new RectF(x1, y1, x2, y2);
+ }
+
+ if(viewMode == VIEW_MODE.NO_FACE_PREPARE ||
+ viewMode == VIEW_MODE.REPEAT_NO_FACE_PREPARE ||
+ viewMode == VIEW_MODE.TO_FACE_CIRCLE ||
+ viewMode == VIEW_MODE.FACE_CIRCLE_TO_NO_FACE) {
+
+ RectF scaleRoiRect = roiViewRect;
+ if(viewMode == VIEW_MODE.NO_FACE_PREPARE ||
+ viewMode == VIEW_MODE.REPEAT_NO_FACE_PREPARE ||
+ (viewMode == VIEW_MODE.TO_FACE_CIRCLE && animateValue > 1.0f)) {
+ CaptureView.scale(scaleRoiRect, animateValue);
+ }
+
+ float lineWidth1 = scaleRoiRect.width() / 5;
+ float lineWidthOffset1 = 0;
+ if(viewMode == VIEW_MODE.FACE_CIRCLE ||
+ (viewMode == VIEW_MODE.TO_FACE_CIRCLE && animateValue < 1.0f) ||
+ viewMode == VIEW_MODE.FACE_CIRCLE_TO_NO_FACE) {
+ lineWidth1 = lineWidth1 * animateValue;
+ lineWidthOffset1 = scaleRoiRect.width() / 2 * (1 - animateValue);
+ }
+ float lineHeight1 = scaleRoiRect.height() / 5;
+ float lineHeightOffset1 = 0;
+ if(viewMode == VIEW_MODE.FACE_CIRCLE ||
+ (viewMode == VIEW_MODE.TO_FACE_CIRCLE && animateValue < 1.0f) ||
+ viewMode == VIEW_MODE.FACE_CIRCLE_TO_NO_FACE) {
+ lineHeight1 = lineHeight1 * animateValue;
+ lineHeightOffset1 = scaleRoiRect.height() / 2 * (1 - animateValue);
+ }
+ float quad_r1 = scaleRoiRect.width() / 12;
+ if(viewMode == VIEW_MODE.FACE_CIRCLE ||
+ (viewMode == VIEW_MODE.TO_FACE_CIRCLE && animateValue < 1.0f) ||
+ viewMode == VIEW_MODE.FACE_CIRCLE_TO_NO_FACE) {
+ quad_r1 = scaleRoiRect.width() / 12 + (scaleRoiRect.width() / 2 - scaleRoiRect.width() / 12) * (1 - animateValue) - 20;
+ }
+
+ Paint paint1 = new Paint();
+ paint1.setStyle(Paint.Style.STROKE);
+ paint1.setStrokeWidth(10);
+ paint1.setColor(ContextCompat.getColor(context, R.color.md_theme_dark_onPrimaryContainer));
+ if(viewMode == VIEW_MODE.NO_FACE_PREPARE ||
+ (viewMode == VIEW_MODE.TO_FACE_CIRCLE && animateValue > 1.0f)) {
+ int alpha = Math.min(255, (int)((1.4 - animateValue) / 0.4 * 255));
+ paint1.setAlpha(alpha);
+ } else {
+ paint1.setAlpha(255);
+ }
+ paint1.setAntiAlias(true);
+
+ Path path1 = new Path();
+ path1.moveTo(scaleRoiRect.left, scaleRoiRect.top + lineHeight1 + lineHeightOffset1);
+ path1.lineTo(scaleRoiRect.left, scaleRoiRect.top + quad_r1);
+ path1.arcTo(scaleRoiRect.left, scaleRoiRect.top, scaleRoiRect.left + quad_r1 * 2, scaleRoiRect.top + quad_r1 * 2, 180, 90, false);
+ path1.lineTo(scaleRoiRect.left + lineWidth1 + lineWidthOffset1, scaleRoiRect.top);
+ canvas.drawPath(path1, paint1);
+
+ Path path2 = new Path();
+ path2.moveTo(scaleRoiRect.right, scaleRoiRect.top + lineHeight1 + lineHeightOffset1);
+ path2.lineTo(scaleRoiRect.right, scaleRoiRect.top + quad_r1);
+ path2.arcTo(scaleRoiRect.right - quad_r1 * 2, scaleRoiRect.top, scaleRoiRect.right, scaleRoiRect.top + quad_r1 * 2, 0, -90, false);
+ path2.lineTo(scaleRoiRect.right - lineWidth1 - lineWidthOffset1, scaleRoiRect.top);
+ canvas.drawPath(path2, paint1);
+
+ Path path3 = new Path();
+ path3.moveTo(scaleRoiRect.right, scaleRoiRect.bottom - lineHeight1 - lineHeightOffset1);
+ path3.lineTo(scaleRoiRect.right, scaleRoiRect.bottom - quad_r1);
+ path3.arcTo(scaleRoiRect.right - quad_r1 * 2, scaleRoiRect.bottom - quad_r1 * 2, scaleRoiRect.right, scaleRoiRect.bottom, 0, 90, false);
+ path3.lineTo(scaleRoiRect.right - lineWidth1 - lineWidthOffset1, scaleRoiRect.bottom);
+ canvas.drawPath(path3, paint1);
+
+ Path path4 = new Path();
+ path4.moveTo(scaleRoiRect.left, scaleRoiRect.bottom - lineHeight1 - lineHeightOffset1);
+ path4.lineTo(scaleRoiRect.left, scaleRoiRect.bottom - quad_r1);
+ path4.arcTo(scaleRoiRect.left, scaleRoiRect.bottom - quad_r1 * 2, scaleRoiRect.left + quad_r1 * 2, scaleRoiRect.bottom, 180, -90, false);
+ path4.lineTo(scaleRoiRect.left + lineWidth1 + lineWidthOffset1, roiViewRect.bottom);
+ canvas.drawPath(path4, paint1);
+ }
+
+ if((viewMode == VIEW_MODE.TO_FACE_CIRCLE && animateValue < 1.0f) || viewMode == VIEW_MODE.FACE_CIRCLE_TO_NO_FACE) {
+
+ float start_width = 0.8f * roiViewRect.width() * 0.5f / (float)Math.cos(45 * Math.PI / 180);
+
+ float center_x = roiViewRect.centerX();
+ float center_y = roiViewRect.centerY();
+ float left = center_x - (roiViewRect.width() / 2 * (1 - animateValue) + start_width * animateValue);
+ float top = center_y - (roiViewRect.width() / 2 * (1 - animateValue) + start_width * animateValue);
+ float right = center_x + (roiViewRect.width() / 2 * (1 - animateValue) + start_width * animateValue);
+ float bottom = center_y + (roiViewRect.width() / 2 * (1 - animateValue) + start_width * animateValue);
+ RectF eraseRect = new RectF(left, top, right, bottom);
+ canvas.drawRoundRect(eraseRect, eraseRect.width() / 2, eraseRect.height() / 2, eraserPaint);
+ } else if(viewMode == VIEW_MODE.FACE_CIRCLE) {
+ canvas.drawRoundRect(roiViewRect, roiViewRect.width() / 2, roiViewRect.height() / 2, eraserPaint);
+
+ double centerX = roiViewRect.centerX();
+ double centerY = roiViewRect.centerY();
+
+ for(int i = 0; i < 360; i += 5) {
+
+ double a1 = roiViewRect.width() / 2 + 10;
+ double b1 = roiViewRect.height() / 2 + 10;
+ double a2 = roiViewRect.width() / 2 + 40;
+ double b2 = roiViewRect.height() / 2 + 40;
+
+ double th = i * Math.PI / 180;
+ double x1 = a1 * b1 / Math.sqrt(Math.pow(b1, 2) + Math.pow(a1, 2) * Math.tan(th) * Math.tan(th));
+ double x2 = a2 * b2 / Math.sqrt(Math.pow(b2, 2) + Math.pow(a2, 2) * Math.tan(th) * Math.tan(th));
+ double y1 = Math.sqrt(1 - (x1 / a1) * (x1 / a1)) * b1;
+ double y2 = Math.sqrt(1 - (x1 / a1) * (x1 / a1)) * b2;
+
+ if((i % 360) > 90 && (i % 360) < 270) {
+ x1 = -x1;
+ x2 = -x2;
+ }
+
+ if((i % 360) > 180 && (i % 360) < 360) {
+ y1 = -y1;
+ y2 = -y2;
+ }
+
+ canvas.drawLine((float)(centerX + x1), (float)(centerY - y1), (float)(centerX + x2), (float)(centerY - y2), outSideActiveRoundPaint);
+ }
+
+ if(faceBoxes != null && faceBoxes.size() > 0) {
+ Paint paint1 = new Paint();
+ paint1.setStyle(Paint.Style.FILL_AND_STROKE);
+ paint1.setStrokeWidth(6);
+ paint1.setColor(ContextCompat.getColor(context, R.color.md_theme_dark_onPrimaryContainer));
+ paint1.setAlpha(128);
+ paint1.setAntiAlias(true);
+
+ FaceBox faceBox = faceBoxes.get(0);
+ double yaw = faceBox.yaw;
+ double pitch = faceBox.pitch;
+
+ Path path1 = new Path();
+ path1.moveTo(roiViewRect.centerX(), roiViewRect.top);
+ path1.quadTo(roiViewRect.centerX() - roiViewRect.width() * (float) Math.sin(yaw * Math.PI / 180), roiViewRect.centerY(), roiViewRect.centerX(), roiViewRect.bottom);
+ path1.quadTo(roiViewRect.centerX() - roiViewRect.width() * (float) Math.sin(yaw * Math.PI / 180) / 3, roiViewRect.centerY(), roiViewRect.centerX(), roiViewRect.top);
+ canvas.drawPath(path1, paint1);
+
+ Path path2 = new Path();
+ path2.moveTo(roiViewRect.left, roiViewRect.centerY());
+ path2.quadTo(roiViewRect.centerX(), roiViewRect.centerY() + roiViewRect.width() * (float) Math.sin(pitch * Math.PI / 180), roiViewRect.right, roiViewRect.centerY());
+ path2.quadTo(roiViewRect.centerX(), roiViewRect.centerY() + roiViewRect.width() * (float) Math.sin(pitch * Math.PI / 180) / 3, roiViewRect.left, roiViewRect.centerY());
+ canvas.drawPath(path2, paint1);
+ }
+ } else if(viewMode == VIEW_MODE.FACE_CAPTURE_PREPARE) {
+
+ RectF borderRect = new RectF(roiViewRect);
+ CaptureView.scale(borderRect, 1.04f);
+ Paint paint1 = new Paint();
+ paint1.setStyle(Paint.Style.FILL);
+ paint1.setColor(ContextCompat.getColor(context, R.color.md_theme_dark_onTertiary));
+ paint1.setAntiAlias(true);
+ canvas.drawCircle(borderRect.centerX(), borderRect.centerY(), borderRect.width() / 2, paint1);
+
+ RectF innerRect = new RectF(roiViewRect);
+ CaptureView.scale(innerRect, 1.0f - animateValue);
+ canvas.drawRoundRect(innerRect, innerRect.width() / 2, innerRect.height() / 2, eraserPaint);
+ } else if(viewMode == VIEW_MODE.FACE_CAPTURE_DONE) {
+ RectF borderRect = new RectF(roiViewRect);
+ CaptureView.scale(borderRect, 0.8f);
+
+ Rect roiViewRectSrc = new Rect();
+ borderRect.round(roiViewRectSrc);
+
+ Paint paint1 = new Paint();
+ paint1.setStyle(Paint.Style.STROKE);
+ paint1.setColor(ContextCompat.getColor(context, R.color.md_theme_dark_onTertiary));
+ paint1.setStrokeWidth(15);
+ paint1.setAntiAlias(true);
+
+ canvas.translate(0, (getWidth() / 5 - roiViewRect.top) * animateValue);
+ canvas.drawBitmap(roiBitmap, new Rect(0, 0, roiBitmap.getWidth(), roiBitmap.getHeight()), borderRect, null);
+ canvas.drawCircle(borderRect.centerX(), borderRect.centerY(), borderRect.width() / 2, paint1);
+ }
+ }
+
+ private static void scale(RectF rect, float factor){
+ float diffHorizontal = (rect.right-rect.left) * (factor-1f);
+ float diffVertical = (rect.bottom-rect.top) * (factor-1f);
+
+ rect.top -= diffVertical/2f;
+ rect.bottom += diffVertical/2f;
+
+ rect.left -= diffHorizontal/2f;
+ rect.right += diffHorizontal/2f;
+ }
+
+ public static RectF getROIRect(Size frameSize) {
+ int margin = frameSize.getWidth() / 6;
+ int rectHeight = (frameSize.getWidth() - 2 * margin) * 6 / 5;
+
+ RectF roiRect = new RectF(margin, (frameSize.getHeight() - rectHeight) / 2,
+ frameSize.getWidth() - margin, (frameSize.getHeight() - rectHeight) / 2 + rectHeight);
+ return roiRect;
+ }
+
+ public static RectF getROIRect1(Size frameSize) {
+ int margin = frameSize.getWidth() / 6;
+ int rectHeight = (frameSize.getWidth() - 2 * margin);
+
+ RectF roiRect = new RectF(margin, (frameSize.getHeight() - rectHeight) / 2,
+ frameSize.getWidth() - margin, (frameSize.getHeight() - rectHeight) / 2 + rectHeight);
+ return roiRect;
+ }
+}
diff --git a/app/src/main/java/com/kbyai/faceattribute/DBManager.java b/app/src/main/java/com/kbyai/faceattribute/DBManager.java
new file mode 100644
index 0000000..5510284
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/DBManager.java
@@ -0,0 +1,95 @@
+package com.kbyai.faceattribute;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+
+public class DBManager extends SQLiteOpenHelper {
+
+ public static ArrayList personList = new ArrayList();
+
+ public DBManager(Context context) {
+ super(context, "mydb" , null, 1);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ // TODO Auto-generated method stub
+ db.execSQL(
+ "create table person " +
+ "(name text, face blob, templates blob)"
+ );
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // TODO Auto-generated method stub
+ db.execSQL("DROP TABLE IF EXISTS person");
+ onCreate(db);
+ }
+
+ public void insertPerson (String name, Bitmap face, byte[] templates) {
+
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ face.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
+ byte[] faceJpg = byteArrayOutputStream.toByteArray();
+
+ SQLiteDatabase db = this.getWritableDatabase();
+ ContentValues contentValues = new ContentValues();
+ contentValues.put("name", name);
+ contentValues.put("face", faceJpg);
+ contentValues.put("templates", templates);
+ db.insert("person", null, contentValues);
+
+ personList.add(new Person(name, face, templates));
+ }
+
+ public Integer deletePerson (String name) {
+ for(int i = 0; i < personList.size(); i ++) {
+ if(personList.get(i).name == name) {
+ personList.remove(i);
+ i --;
+ }
+ }
+
+ SQLiteDatabase db = this.getWritableDatabase();
+ return db.delete("person",
+ "name = ? ",
+ new String[] { name });
+ }
+
+ public Integer clearDB () {
+ personList.clear();
+
+ SQLiteDatabase db = this.getWritableDatabase();
+ db.execSQL("delete from person");
+ return 0;
+ }
+
+ public void loadPerson() {
+ personList.clear();
+
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor res = db.rawQuery( "select * from person", null );
+ res.moveToFirst();
+
+ while(res.isAfterLast() == false){
+ String name = res.getString(res.getColumnIndex("name"));
+ byte[] faceJpg = res.getBlob(res.getColumnIndex("face"));
+ byte[] templates = res.getBlob(res.getColumnIndex("templates"));
+ Bitmap face = BitmapFactory.decodeByteArray(faceJpg, 0, faceJpg.length);
+
+ Person person = new Person(name, face, templates);
+ personList.add(person);
+
+ res.moveToNext();
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbyai/faceattribute/FACE_CAPTURE_STATE.java b/app/src/main/java/com/kbyai/faceattribute/FACE_CAPTURE_STATE.java
new file mode 100644
index 0000000..16db429
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/FACE_CAPTURE_STATE.java
@@ -0,0 +1,5 @@
+package com.kbyai.faceattribute;
+
+public enum FACE_CAPTURE_STATE {
+ NO_FACE, MULTIPLE_FACES, FIT_IN_CIRCLE, MOVE_CLOSER, NO_FRONT, FACE_OCCLUDED, EYE_CLOSED, MOUTH_OPENED, SPOOFED_FACE, CAPTURE_OK
+}
diff --git a/app/src/main/java/com/kbyai/faceattribute/FaceView.java b/app/src/main/java/com/kbyai/faceattribute/FaceView.java
new file mode 100644
index 0000000..2bb09e0
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/FaceView.java
@@ -0,0 +1,108 @@
+package com.kbyai.faceattribute;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Size;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.kbyai.faceattribute.SettingsActivity;
+import com.kbyai.facesdk.FaceBox;
+
+import java.util.List;
+
+public class FaceView extends View {
+
+ private Context context;
+ private Paint realPaint;
+ private Paint spoofPaint;
+
+ private Size frameSize;
+
+ private List faceBoxes;
+
+ public FaceView(Context context) {
+ this(context, null);
+
+ this.context = context;
+ init();
+ }
+
+ public FaceView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ this.context = context;
+
+ init();
+ }
+
+ public void init() {
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
+ realPaint = new Paint();
+ realPaint.setStyle(Paint.Style.STROKE);
+ realPaint.setStrokeWidth(3);
+ realPaint.setColor(Color.GREEN);
+ realPaint.setAntiAlias(true);
+ realPaint.setTextSize(50);
+
+ spoofPaint = new Paint();
+ spoofPaint.setStyle(Paint.Style.STROKE);
+ spoofPaint.setStrokeWidth(3);
+ spoofPaint.setColor(Color.RED);
+ spoofPaint.setAntiAlias(true);
+ spoofPaint.setTextSize(50);
+ }
+
+ public void setFrameSize(Size frameSize)
+ {
+ this.frameSize = frameSize;
+ }
+
+ public void setFaceBoxes(List faceBoxes)
+ {
+ this.faceBoxes = faceBoxes;
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (frameSize != null && faceBoxes != null) {
+ float x_scale = this.frameSize.getWidth() / (float)canvas.getWidth();
+ float y_scale = this.frameSize.getHeight() / (float)canvas.getHeight();
+
+ for (int i = 0; i < faceBoxes.size(); i++) {
+ FaceBox faceBox = faceBoxes.get(i);
+
+ if (faceBox.liveness < SettingsActivity.getLivenessThreshold(context))
+ {
+ spoofPaint.setStrokeWidth(3);
+ spoofPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ canvas.drawText("SPOOF " + faceBox.liveness, (faceBox.x1 / x_scale) + 10, (faceBox.y1 / y_scale) - 30, spoofPaint);
+
+ spoofPaint.setStrokeWidth(5);
+ spoofPaint.setStyle(Paint.Style.STROKE);
+ canvas.drawRect(new Rect((int)(faceBox.x1 / x_scale), (int)(faceBox.y1 / y_scale),
+ (int)(faceBox.x2 / x_scale), (int)(faceBox.y2 / y_scale)), spoofPaint);
+ }
+ else
+ {
+ realPaint.setStrokeWidth(3);
+ realPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ canvas.drawText("REAL " + faceBox.liveness, (faceBox.x1 / x_scale) + 10, (faceBox.y1 / y_scale) - 30, realPaint);
+
+ realPaint.setStyle(Paint.Style.STROKE);
+ realPaint.setStrokeWidth(5);
+ canvas.drawRect(new Rect((int)(faceBox.x1 / x_scale), (int)(faceBox.y1 / y_scale),
+ (int)(faceBox.x2 / x_scale), (int)(faceBox.y2 / y_scale)), realPaint);
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/kbyai/faceattribute/MainActivity.kt b/app/src/main/java/com/kbyai/faceattribute/MainActivity.kt
new file mode 100644
index 0000000..bee2e83
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/MainActivity.kt
@@ -0,0 +1,175 @@
+package com.kbyai.faceattribute
+
+import android.content.Intent
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.Bundle
+import android.view.View
+import android.widget.*
+import androidx.appcompat.app.AppCompatActivity
+import com.kbyai.faceattribute.SettingsActivity
+import com.kbyai.facesdk.FaceBox
+import com.kbyai.facesdk.FaceDetectionParam
+import com.kbyai.facesdk.FaceSDK
+import kotlin.random.Random
+
+class MainActivity : AppCompatActivity() {
+
+ companion object {
+ private val SELECT_PHOTO_REQUEST_CODE = 1
+ private val SELECT_ATTRIBUTE_REQUEST_CODE = 2
+ }
+
+ private lateinit var dbManager: DBManager
+ private lateinit var textWarning: TextView
+ private lateinit var personAdapter: PersonAdapter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ textWarning = findViewById(R.id.textWarning)
+
+
+ var ret = FaceSDK.setActivation(
+ "fGMbqRWAN9PrnQBHd3JtdbNCKJ75REHRN4yenuntm9SghMVrQztH8IQIObnN3hJc6RitR139CwnP\n" +
+ "P/hUVlINXCk48PkGrTJlNsFUm5ErOXL2QWw7IUzQow/DALUwvKOR4Qpz3i0lHKVlrFqMOKb4y3DH\n" +
+ "Dhb/Fh6KLywr5aWy5Lwv/hutFqe6gao9xVqpbOLq2yP+OIjPpW0teMxEjSKGhuQftp7lV9tEnv9B\n" +
+ "lAI75/ElCUYb6vxWCqZFSGLLiDuEyTbz7Npz1rhuQkwmotgLTYrij0zzIt79TccUve9lx2xl/fqS\n" +
+ "y6YUynuO4VN/awOJQFMv4HpFVFVupmU/ezM7Tg=="
+ )
+
+ if (ret == FaceSDK.SDK_SUCCESS) {
+ ret = FaceSDK.init(assets)
+ }
+
+ if (ret != FaceSDK.SDK_SUCCESS) {
+ textWarning.setVisibility(View.VISIBLE)
+ if (ret == FaceSDK.SDK_LICENSE_KEY_ERROR) {
+ textWarning.setText("Invalid license!")
+ } else if (ret == FaceSDK.SDK_LICENSE_APPID_ERROR) {
+ textWarning.setText("Invalid error!")
+ } else if (ret == FaceSDK.SDK_LICENSE_EXPIRED) {
+ textWarning.setText("License expired!")
+ } else if (ret == FaceSDK.SDK_NO_ACTIVATED) {
+ textWarning.setText("No activated!")
+ } else if (ret == FaceSDK.SDK_INIT_ERROR) {
+ textWarning.setText("Init error!")
+ }
+ }
+
+ dbManager = DBManager(this)
+ dbManager.loadPerson()
+
+ personAdapter = PersonAdapter(this, DBManager.personList)
+ val listView: ListView = findViewById(R.id.listPerson) as ListView
+ listView.setAdapter(personAdapter)
+
+ findViewById