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..df168a2
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,65 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ namespace 'com.kbyai.faceattribute'
+ compileSdk 34
+
+ defaultConfig {
+ applicationId "com.kbyai.activelive"
+ minSdk 24
+ targetSdk 34
+ versionCode 5
+ versionName "1.4"
+
+ 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 'com.squareup.okhttp3:okhttp:4.9.3'
+ implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'
+ implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.9.1'
+
+ implementation project(path: ':libfacesdk')
+
+ // implementation 'io.fotoapparat:fotoapparat:2.7.0'
+ implementation project(path: ':libfotoapparat')
+
+ 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..f9c9bd2
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..fa7515f
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/AboutActivity.kt
@@ -0,0 +1,149 @@
+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)
+ }
+ }
+
+ findViewById(R.id.txtSkype).setOnClickListener {
+ val general = Intent(Intent.ACTION_VIEW, Uri.parse("https://com.skype/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://join.skype.com/invite/OffY2r1NUFev"))
+ 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.txtGitHub).setOnClickListener {
+ val general = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/kby-ai"))
+ 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://github.com/kby-ai"))
+ 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/CameraActivity.kt b/app/src/main/java/com/kbyai/faceattribute/CameraActivity.kt
new file mode 100644
index 0000000..0b30664
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/CameraActivity.kt
@@ -0,0 +1,1385 @@
+package com.kbyai.faceattribute
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.PackageManager
+import android.graphics.*
+import android.os.*
+import android.util.Log
+import android.util.Size
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.ProgressBar
+import android.widget.TextView
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.preference.PreferenceManager
+import com.kbyai.faceattribute.FaceRectTransformer
+import com.kbyai.faceattribute.FaceRectView
+import com.kbyai.faceattribute.R
+import com.kbyai.faceattribute.SettingsActivity
+import com.kbyai.faceattribute.Utils
+import com.kbyai.facesdk.FaceSDK
+import com.kbyai.facesdk.FaceBox
+import com.kbyai.facesdk.FaceDetectionParam
+
+import io.fotoapparat.Fotoapparat
+import io.fotoapparat.parameter.Resolution
+import io.fotoapparat.preview.Frame
+import io.fotoapparat.selector.front
+import io.fotoapparat.selector.highestResolution
+import io.fotoapparat.util.FrameProcessor
+import io.fotoapparat.view.CameraView
+import okhttp3.*
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.Request.*
+import okhttp3.logging.HttpLoggingInterceptor
+import java.io.*
+import java.security.KeyStore
+import java.security.SecureRandom
+import java.security.cert.CertificateFactory
+import java.security.cert.X509Certificate
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.ThreadPoolExecutor
+import java.util.concurrent.TimeUnit
+import javax.net.ssl.SSLContext
+import javax.net.ssl.TrustManagerFactory
+import javax.net.ssl.X509TrustManager
+import kotlin.concurrent.thread
+import kotlin.math.acos
+import kotlin.math.sqrt
+
+
+class CameraActivity : AppCompatActivity() {
+
+ private enum class State {
+ IDLE,
+ START_LOOK_UP,
+ LOOK_UP,
+ START_NOD,
+ NOD,
+ START_LOOK_UP_NOD,
+ LOOK_UP_NOD,
+ START_NOD_LOOK_UP,
+ NOD_LOOK_UP,
+ START_ZOOM_IN,
+ ZOOM_IN,
+ START_ZOOM_OUT,
+ ZOOM_OUT,
+ START_ZOOM_IN_OUT,
+ ZOOM_IN_OUT,
+ START_ZOOM_OUT_IN,
+ ZOOM_OUT_IN,
+ START_MOUTH,
+ MOUTH,
+ START_EYE_BLINK,
+ EYE_BLINK,
+ START_TURN_LEFT,
+ TURN_LEFT,
+ START_TURN_RIGHT,
+ TURN_RIGHT,
+ START_TURN_LEFT_RIGHT,
+ TURN_LEFT_RIGHT,
+ START_TURN_RIGHT_LEFT,
+ TURN_RIGHT_LEFT,
+ LIVENESS_CHECK_COMPLETED,
+ END
+ }
+
+ public enum class ROI_CHECK_RESULT {
+ ROI_NO_FACE,
+ ROI_INTERSECTS,
+ ROI_SMALL_FACE,
+ ROI_FACE_OK
+ }
+
+ companion object {
+ private val TAG = CameraActivity::class.simpleName
+ private const val ALLOW_NO_FACE_TIMES = 2
+ private const val FACING_CAMERA_KEEP_TIME = 3000L
+
+ val PREVIEW_WIDTH = 720
+ val PREVIEW_HEIGHT = 1280
+
+ const val RESULT_KEY_FACING_CAMERA_IMAGE_PATH = "facing_image"
+ const val RESULT_KEY_SMILING_IMAGE_PATH = "smiling_image"
+ const val RESULT_KEY_MOUTH_IMAGE_PATH = "mouth_image"
+ const val RESULT_KEY_SHAKE_IMAGE_PATH = "shake_image"
+
+ const val HANDLE_UPDATE_FACE = 0
+ const val HANDLE_TOAST_SHOW = 1
+ const val HANDLE_SET_TITLE = 2
+ const val HANDLE_VIEW_MODE = 3
+ const val HANDLE_START_TIMER = 4
+ const val HANDLE_STOP_TIMER = 5
+ const val HANDLE_VIBRATOR = 6
+ }
+
+ private lateinit var imageScene: ImageView
+ private lateinit var txtWarning: TextView
+
+ private var warningMessage = ""
+ private val actionsList: List = listOf(State.START_LOOK_UP, State.START_NOD, State.START_LOOK_UP_NOD, State.START_NOD_LOOK_UP,
+ State.START_ZOOM_IN, State.START_ZOOM_OUT, State.START_ZOOM_IN_OUT, State.START_ZOOM_OUT_IN,
+ State.START_MOUTH, State.START_EYE_BLINK, State.START_TURN_LEFT, State.START_TURN_RIGHT, State.START_TURN_LEFT_RIGHT, State.START_TURN_RIGHT_LEFT)
+ private var actionsIdxs = ArrayList()
+ private var currentActionIdx = 0
+ private var context: Context? = null
+ private var cameraView: CameraView? = null
+ private var textView:TextView?=null
+ private var txtTimer: TextView?= null
+ private var progressTimer: ProgressBar? = null
+ private var rectanglesView: FaceRectView? = null
+ private var faceRectTransformer: FaceRectTransformer? = null
+ private var frontFotoapparat: Fotoapparat? = null
+
+ lateinit var lytPrepareFaceCapture: ConstraintLayout
+ lateinit var imgFaceCapture: ImageView
+ lateinit var txtFaceCapture: TextView
+ lateinit var txtFaceCaptureWarning: TextView
+
+ private var state: State = State.IDLE
+ private var timer: CountDownTimer? = null
+
+ private var facingStartTime = 0L
+ private var hasShakeToLeft = false
+ private var hasShakeToRight = false
+ private var hasLookUp = false
+ private var hasLookNod = false
+ private var hasZoomIn = false
+ private var hasZoomOut = false
+ private var lastEyeClosed = false
+ private var faceCaptured = 0
+
+ private var yawThreshold = 0.0f
+ private var rollThreshold = 0.0f
+ private var pitchThreshold = 0.0f
+ private var maxLivenessCount = 0
+ private var minimumLivenessRange = 0
+ private var maximumLivenessRange = 0
+ private var timeoutEachActions = 0
+ private var timeoutFaceCapture = 0
+ private var hasPostProcess = false
+ private var hasCoolDown = false
+ private var postProcessAddress = ""
+ private var minimumLuminance = 0
+ private var maximumLuminance = 0
+
+ private val mHandler: Handler = object : Handler() {
+ override fun handleMessage(msg: Message) {
+ val i: Int = msg.what
+ if (i == HANDLE_UPDATE_FACE) {
+ var detectionResult = msg.obj as ArrayList
+ rectanglesView!!.setHasFace(detectionResult.count() > 0)
+ } else if(i == HANDLE_TOAST_SHOW) {
+ val str: String = msg.obj as String
+ Toast.makeText(
+ context,
+ str,
+ Toast.LENGTH_SHORT
+ ).show()
+ } else if(i == HANDLE_SET_TITLE) {
+ val str: String = msg.obj as String
+ textView!!.text = str
+ } else if(i == HANDLE_VIEW_MODE) {
+ rectanglesView!!.setMode(msg.obj as FaceRectView.DispState)
+ } else if(i == HANDLE_START_TIMER) {
+ val timeOut: Int = msg.obj as Int
+ startCaptureTimer(timeOut)
+ } else if(i == HANDLE_STOP_TIMER) {
+ timer?.cancel()
+
+ progressTimer?.progress = timeoutEachActions
+ txtTimer?.text = "" + timeoutEachActions + "s"
+ } else if(i == HANDLE_VIBRATOR) {
+ val v = getSystemService(VIBRATOR_SERVICE) as Vibrator
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ v.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE))
+ } else {
+ //deprecated in API 26
+ v.vibrate(500)
+ }
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_camera)
+
+ context = this
+ cameraView = findViewById(R.id.camera_view) as CameraView
+ rectanglesView = findViewById(R.id.rectanglesView) as FaceRectView
+ textView = findViewById(R.id.textView) as TextView
+ txtTimer = findViewById(R.id.txtTimer) as TextView
+ txtWarning = findViewById(R.id.txtWarning)
+ progressTimer = findViewById(R.id.progressBar) as ProgressBar
+ lytPrepareFaceCapture = findViewById(R.id.lytPrepareFaceCapture)
+ imgFaceCapture = findViewById(R.id.imgFaceCapture)
+ txtFaceCapture = findViewById(R.id.txtFaceCaptureResult)
+ txtFaceCaptureWarning = findViewById(R.id.txtFaceCaptureWarning)
+
+ var tempActionsIdxs = ArrayList()
+ for(i in actionsList.indices)
+ tempActionsIdxs.add(i)
+
+ for(i in actionsList.indices) {
+ val rand = (Math.random() * 100).toInt() % tempActionsIdxs.size
+ actionsIdxs.add(tempActionsIdxs.get(rand))
+ tempActionsIdxs.removeAt(rand)
+ }
+
+ val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
+ yawThreshold = sharedPreferences.getString("valid_yaw_angle", "" + SettingsActivity.DEFAULT_VALID_YAW_ANGLE)!!.toFloat()
+ rollThreshold = sharedPreferences.getString("valid_roll_angle", "" + SettingsActivity.DEFAULT_VALID_ROLL_ANGLE)!!.toFloat()
+ pitchThreshold = sharedPreferences.getString("valid_pitch_angle", "" + SettingsActivity.DEFAULT_VALID_PITCH_ANGLE)!!.toFloat()
+ minimumLivenessRange = sharedPreferences.getString("minimum_range", "" + SettingsActivity.DEFAULT_MINIMUM_RANGE)!!.toInt()
+ maximumLivenessRange = sharedPreferences.getString("maximum_range", "" + SettingsActivity.DEFAULT_MAXIMUM_RANGE)!!.toInt()
+ maxLivenessCount = minimumLivenessRange + (Math.random() * (maximumLivenessRange + 1)).toInt() % (maximumLivenessRange - minimumLivenessRange + 1)
+ timeoutEachActions = sharedPreferences.getString("liveness_timeout", "" + SettingsActivity.DEFAULT_TIMEOUT_EACH_ACTION)!!.toInt()
+ timeoutFaceCapture = sharedPreferences.getString("face_capture_timeout", "" + SettingsActivity.DEFAULT_TIMEOUT_FACE_CAPTURE)!!.toInt()
+ hasPostProcess = sharedPreferences.getBoolean("post_process_enable", SettingsActivity.DEFAULT_POST_PROCESS_ENABLE)
+ hasCoolDown = sharedPreferences.getBoolean("cool_down_enable", SettingsActivity.DEFAULT_COOL_DOWN_ENABLE)
+ postProcessAddress = sharedPreferences.getString("address_of_api", SettingsActivity.DEFAULT_POST_PROCESS_ADDRESS).toString()
+ minimumLuminance = sharedPreferences.getString("minimum_range_luminance", "" + SettingsActivity.DEFAULT_MIN_RANGE_LUM)!!.toInt()
+ maximumLuminance = sharedPreferences.getString("maximum_range_luminance", "" + SettingsActivity.DEFAULT_MAX_RANGE_LUM)!!.toInt()
+
+ progressTimer?.min = 0
+ progressTimer?.max = timeoutEachActions
+ progressTimer?.progress = timeoutEachActions
+ txtTimer?.text = "" + timeoutEachActions + "s"
+
+ frontFotoapparat = Fotoapparat.with(this)
+ .into(cameraView!!)
+ .lensPosition(front())
+ .frameProcessor(SampleFrameProcessor())
+ .previewResolution { Resolution(PREVIEW_HEIGHT,PREVIEW_WIDTH) }
+ .build()
+
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_DENIED
+ ) {
+ ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 1)
+ } else {
+ frontFotoapparat!!.start()
+ }
+ }
+
+
+ override fun onStop() {
+ super.onStop()
+ timer?.cancel()
+ try {
+ frontFotoapparat!!.stop()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_GRANTED
+ ) {
+ frontFotoapparat!!.start()
+ }
+ }
+
+ fun adjustPreview(width: Int, height: Int) : Boolean{
+ if(faceRectTransformer == null) {
+ val frameSize: Size = Size(width, height);
+ if(cameraView!!.measuredWidth == 0)
+ return false;
+
+ var displayOrientation: Int = 90;
+ adjustPreviewViewSize (cameraView!!,
+ cameraView!!, rectanglesView!!,
+ Size(frameSize.width, frameSize.height), displayOrientation, 1.0f);
+
+ faceRectTransformer = FaceRectTransformer (
+ frameSize.height, frameSize.width,
+ cameraView!!.getLayoutParams().width, cameraView!!.getLayoutParams().height,
+ 0, 1, false,
+ false,
+ false);
+
+ return true;
+ }
+
+ return true;
+ }
+
+ private fun adjustPreviewViewSize(
+ rgbPreview: View,
+ previewView: View,
+ faceRectView: FaceRectView,
+ previewSize: Size,
+ displayOrientation: Int,
+ scale: Float
+ ): ViewGroup.LayoutParams? {
+ val layoutParams = previewView.layoutParams
+ val measuredWidth = previewView.measuredWidth
+ val measuredHeight = previewView.measuredHeight
+ layoutParams.width = measuredWidth
+ layoutParams.height = measuredHeight
+// previewView.layoutParams = layoutParams
+
+ faceRectView.layoutParams.width = measuredWidth
+ faceRectView.layoutParams.height = measuredHeight
+ return layoutParams
+ }
+
+ /* access modifiers changed from: private */ /* access modifiers changed from: public */
+ private fun sendMessage(w: Int, o: Any?) {
+ val message = Message()
+ message.what = w as Int
+ message.obj = o
+ mHandler.sendMessage(message)
+ }
+
+ inner class SampleFrameProcessor : FrameProcessor {
+ var frThreadQueue: LinkedBlockingQueue? = null
+ var frExecutor: ExecutorService? = null
+ init {
+ frThreadQueue = LinkedBlockingQueue(1)
+ frExecutor = ThreadPoolExecutor(
+ 1, 1, 0, TimeUnit.MILLISECONDS, frThreadQueue
+ ) { r: Runnable? ->
+ val t = Thread(r)
+ t.name = "frThread-" + t.id
+ t
+ }
+ }
+
+ override fun invoke(frame: Frame) {
+ if(state == State.END)
+ return
+
+ val bitmap = FaceSDK.yuv2Bitmap(frame.image, frame.size.width, frame.size.height, 7)
+
+ val faceDetectionParam = FaceDetectionParam()
+ faceDetectionParam.check_face_occlusion = true
+ faceDetectionParam.check_eye_closeness = true
+ faceDetectionParam.check_mouth_opened = true
+ val faceResults = FaceSDK.faceDetection(bitmap, faceDetectionParam)
+ Log.e("TestEngine", "face result count " + faceResults.size)
+ val faceCount = faceResults?.count() ?: 0
+ var face: FaceBox? = null
+ if (faceResults != null && !faceResults.isEmpty()) {
+ face = faceResults[0]
+ }
+
+ if (faceCount == 0) {
+ if(state != State.IDLE && state != State.LIVENESS_CHECK_COMPLETED) {
+ state = State.IDLE
+ endProcess("Liveness check failed")
+ return
+ }
+ } else if (faceCount > 1) {
+// endProcess("Please make sure there is only one face on the screen.")
+// return
+ }
+
+ when (state) {
+ State.IDLE -> {
+ setTitle("Place your face in center")
+ setViewMode(FaceRectView.DispState.NO_FACE)
+
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setWarning("No face")
+ facingStartTime = 0L
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setWarning("Fit in circle")
+ facingStartTime = 0L
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setWarning("Move closer")
+ facingStartTime = 0L
+ } else if(!isFacingCamera(face)) {
+ setWarning("See front")
+ facingStartTime = 0L
+ } else {
+ if(facingStartTime == 0L) {
+ facingStartTime = System.currentTimeMillis()
+ setWarning("")
+ } else if(System.currentTimeMillis() - facingStartTime >= FACING_CAMERA_KEEP_TIME){
+
+ postProcess(state.toString())
+ gotoNextAction()
+ }
+ }
+ }
+ State.START_LOOK_UP -> {
+ setViewMode(FaceRectView.DispState.ROUND_NORMAL)
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setTitle( "Place your face in center")
+ setWarning("Fit in circle")
+ } else if(!isFacingCamera(face)) {
+ setTitle( "Place your face in center")
+ setWarning("See front")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("Move closer")
+ } else {
+ setTitle("Look Up")
+ setWarning("")
+ startTimer(timeoutEachActions)
+ state = State.LOOK_UP
+ hasLookUp = false
+ }
+ }
+ State.LOOK_UP -> {
+ val pitch = face?.pitch ?: 0f
+ val thresholdLookup = -pitchThreshold
+ if (pitch < thresholdLookup && !hasLookUp) {
+ hasLookUp = true
+ }
+
+ if (hasLookUp) {
+ postProcess(state.toString())
+ gotoNextAction()
+ }
+ }
+ State.START_NOD -> {
+ setViewMode(FaceRectView.DispState.ROUND_NORMAL)
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setTitle( "Place your face in center")
+ setWarning("Fit in circle")
+ } else if(!isFacingCamera(face)) {
+ setTitle( "Place your face in center")
+ setWarning("See front")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("Move closer")
+ } else {
+ setTitle( "Look Down")
+ setWarning("")
+ startTimer(timeoutEachActions)
+ state = State.NOD
+ hasLookNod = false
+ }
+ }
+ State.NOD -> {
+ val pitch = face?.pitch ?: 0f
+ val thresholdLookNod = pitchThreshold
+ if (pitch > thresholdLookNod && !hasLookNod) {
+ hasLookNod = true
+ }
+ if (hasLookNod) {
+ postProcess(state.toString())
+ gotoNextAction()
+ }
+ }
+ State.START_LOOK_UP_NOD -> {
+ setViewMode(FaceRectView.DispState.ROUND_NORMAL)
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setTitle( "Place your face in center")
+ setWarning("Fit in circle")
+ } else if(!isFacingCamera(face)) {
+ setTitle( "Place your face in center")
+ setWarning("See front")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("Move closer")
+ } else {
+ setTitle( "Look Up => Down")
+ setWarning("")
+ startTimer(timeoutEachActions)
+ state = State.LOOK_UP_NOD
+ hasLookUp = false
+ hasLookNod = false
+ }
+ }
+ State.LOOK_UP_NOD -> {
+ val pitch = face?.pitch ?: 0f
+ val thresholdLookup = -pitchThreshold
+ if (pitch < thresholdLookup && !hasLookUp && !hasLookNod) {
+ hasLookUp = true
+ postProcess(state.toString() + "1")
+ }
+
+ val thresholdLookNod = 12f
+ if (pitch > thresholdLookNod && hasLookUp && !hasLookNod) {
+ hasLookNod = true
+ postProcess(state.toString() + "2")
+ }
+
+ if (hasLookUp && hasLookNod) {
+ gotoNextAction()
+ }
+ }
+ State.START_NOD_LOOK_UP -> {
+ setViewMode(FaceRectView.DispState.ROUND_NORMAL)
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setTitle( "Place your face in center")
+ setWarning("Fit in circle")
+ } else if(!isFacingCamera(face)) {
+ setTitle( "Place your face in center")
+ setWarning("See front")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("Move closer")
+ } else {
+ setTitle( "Look Down => Up")
+ setWarning("")
+ startTimer(timeoutEachActions)
+ state = State.NOD_LOOK_UP
+ hasLookUp = false
+ hasLookNod = false
+ }
+ }
+ State.NOD_LOOK_UP -> {
+ val pitch = face?.pitch ?: 0f
+ val thresholdLookNod = pitchThreshold
+ if (pitch > thresholdLookNod && !hasLookUp && !hasLookNod) {
+ hasLookNod = true
+ postProcess(state.toString() + "1")
+ }
+
+ val thresholdLookup = -pitchThreshold
+ if (pitch < thresholdLookup && !hasLookUp && hasLookNod) {
+ hasLookUp = true
+ postProcess(state.toString() + "2")
+ }
+
+ if (hasLookUp && hasLookNod) {
+ gotoNextAction()
+ }
+ }
+ State.START_ZOOM_IN -> {
+ setTitle("Zoom In")
+ setViewMode(FaceRectView.DispState.ROUND_ZOOM_IN)
+ startTimer(timeoutEachActions)
+ state = State.ZOOM_IN
+ }
+ State.ZOOM_IN -> {
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setWarning("Fit in circle")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setWarning("Move closer")
+ } else {
+ postProcess(state.toString())
+ gotoNextAction()
+ }
+ }
+ State.START_ZOOM_IN_OUT -> {
+ setTitle("Zoom In => Out")
+ setViewMode(FaceRectView.DispState.ROUND_ZOOM_IN)
+ startTimer(timeoutEachActions)
+ state = State.ZOOM_IN_OUT
+ hasZoomIn = false
+ hasZoomOut = false
+ }
+ State.ZOOM_IN_OUT -> {
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setWarning("Fit in circle")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setWarning("Move closer")
+ } else {
+
+ if(hasZoomIn == false && hasZoomOut == false) {
+ hasZoomIn = true
+ postProcess(state.toString() + "1")
+ setViewMode(FaceRectView.DispState.ROUND_ZOOM_OUT)
+ }
+ else if(hasZoomIn == true && hasZoomOut == false) {
+ postProcess(state.toString() + "2")
+ gotoNextAction()
+ }
+ }
+ }
+ State.START_ZOOM_OUT -> {
+ setTitle("Zoom Out")
+ setViewMode(FaceRectView.DispState.ROUND_ZOOM_OUT)
+ startTimer(timeoutEachActions)
+ state = State.ZOOM_OUT
+ }
+ State.ZOOM_OUT -> {
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setWarning("Fit in circle")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setWarning("Move closer")
+ } else {
+ postProcess(state.toString())
+ gotoNextAction()
+ }
+ }
+ State.START_ZOOM_OUT_IN -> {
+ setTitle("Zoom Out => In")
+ setViewMode(FaceRectView.DispState.ROUND_ZOOM_OUT)
+ startTimer(timeoutEachActions)
+ state = State.ZOOM_OUT_IN
+ hasZoomIn = false
+ hasZoomOut = false
+ }
+ State.ZOOM_OUT_IN -> {
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setWarning("Fit in circle")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setWarning("Move closer")
+ } else {
+ if(hasZoomIn == false && hasZoomOut == false) {
+ hasZoomOut = true
+ setViewMode(FaceRectView.DispState.ROUND_ZOOM_IN)
+ postProcess(state.toString() + "1")
+ } else if(hasZoomIn == false && hasZoomOut == true) {
+ postProcess(state.toString() + "2")
+ gotoNextAction()
+ }
+ }
+ }
+ State.START_MOUTH -> {
+ setViewMode(FaceRectView.DispState.ROUND_NORMAL)
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setTitle( "Place your face in center")
+ setWarning("Fit in circle")
+ } else if(!isFacingCamera(face)) {
+ setTitle( "Place your face in center")
+ setWarning("See front")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("Move closer")
+ } else {
+ setTitle( "Open your mouth")
+ setWarning("")
+
+ startTimer(timeoutEachActions)
+ state = State.MOUTH
+ }
+ }
+ State.MOUTH -> {
+ if (isMouthOpened(face)) {
+ postProcess(state.toString())
+ gotoNextAction()
+ }
+ }
+ State.START_EYE_BLINK -> {
+ setViewMode(FaceRectView.DispState.ROUND_NORMAL)
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setTitle( "Place your face in center")
+ setWarning("Fit in circle")
+ } else if(!isFacingCamera(face)) {
+ setTitle( "Place your face in center")
+ setWarning("See front")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("Move closer")
+ } else {
+ setTitle( "Blink your eyes")
+ setWarning("")
+
+ if(face!!.left_eye_closed < 0.5 && face!!.right_eye_closed < 0.5) {
+ startTimer(timeoutEachActions)
+ state = State.EYE_BLINK
+ }
+ }
+ }
+ State.EYE_BLINK -> {
+ if(isEyeBlinking(face)) {
+ postProcess(state.toString())
+ gotoNextAction()
+ }
+ }
+ State.START_TURN_LEFT -> {
+ setViewMode(FaceRectView.DispState.ROUND_NORMAL)
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setTitle( "Place your face in center")
+ setWarning("Fit in circle")
+ } else if(!isFacingCamera(face)) {
+ setTitle( "Place your face in center")
+ setWarning("See front")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("Move closer")
+ } else {
+ setTitle( "Look Left")
+ setWarning("")
+
+ startTimer(timeoutEachActions)
+ state = State.TURN_LEFT
+ hasShakeToLeft = false
+ }
+ }
+ State.TURN_LEFT -> {
+ val yaw = face?.yaw ?: 0f
+ val thresholdLeft = yawThreshold
+ if (yaw > thresholdLeft && !hasShakeToLeft) {
+ hasShakeToLeft = true
+ }
+ if (hasShakeToLeft) {
+ postProcess(state.toString())
+ gotoNextAction()
+ }
+ }
+ State.START_TURN_RIGHT -> {
+ setViewMode(FaceRectView.DispState.ROUND_NORMAL)
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setTitle( "Place your face in center")
+ setWarning("Fit in circle")
+ } else if(!isFacingCamera(face)) {
+ setTitle( "Place your face in center")
+ setWarning("See front")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("Move closer")
+ } else {
+ setTitle( "Look right")
+ setWarning("")
+
+ startTimer(timeoutEachActions)
+ state = State.TURN_RIGHT
+ hasShakeToRight = false
+ }
+ }
+ State.TURN_RIGHT -> {
+ val yaw = face?.yaw ?: 0f
+ val thresholdRight = -yawThreshold
+ if (yaw < thresholdRight && !hasShakeToRight) {
+ hasShakeToRight = true
+ }
+ if (hasShakeToRight) {
+ postProcess(state.toString())
+ gotoNextAction()
+ }
+ }
+ State.START_TURN_LEFT_RIGHT -> {
+ setViewMode(FaceRectView.DispState.ROUND_NORMAL)
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setTitle( "Place your face in center")
+ setWarning("Fit in circle")
+ } else if(!isFacingCamera(face)) {
+ setTitle( "Place your face in center")
+ setWarning("See front")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("Move closer")
+ } else {
+ setTitle( "Look Left => Right")
+ setWarning("")
+
+ startTimer(timeoutEachActions)
+ state = State.TURN_LEFT_RIGHT
+ hasShakeToLeft = false
+ hasShakeToRight = false
+
+ }
+ }
+ State.TURN_LEFT_RIGHT -> {
+ val yaw = face?.yaw ?: 0f
+ val thresholdLeft = yawThreshold
+ if (yaw > thresholdLeft && !hasShakeToLeft && !hasShakeToRight) {
+ hasShakeToLeft = true
+ postProcess(state.toString() + "1")
+ }
+
+ val thresholdRight = -yawThreshold
+ if (yaw < thresholdRight && hasShakeToLeft && !hasShakeToRight) {
+ hasShakeToRight = true
+ postProcess(state.toString() + "2")
+ }
+
+ if (hasShakeToLeft && hasShakeToRight) {
+ gotoNextAction()
+ }
+ }
+ State.START_TURN_RIGHT_LEFT -> {
+ setViewMode(FaceRectView.DispState.ROUND_NORMAL)
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("No face")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setTitle( "Place your face in center")
+ setWarning("Fit in circle")
+ } else if(!isFacingCamera(face)) {
+ setTitle( "Place your face in center")
+ setWarning("See front")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setTitle( "Place your face in center")
+ setWarning("Move closer")
+ } else {
+ setTitle( "Look Right => Left")
+ setWarning("")
+
+ startTimer(timeoutEachActions)
+ state = State.TURN_RIGHT_LEFT
+ hasShakeToLeft = false
+ hasShakeToRight = false
+
+ }
+ }
+ State.TURN_RIGHT_LEFT -> {
+ val yaw = face?.yaw ?: 0f
+ val thresholdRight = -yawThreshold
+ if (yaw < thresholdRight && !hasShakeToLeft && !hasShakeToRight) {
+ hasShakeToRight = true
+ }
+
+ val thresholdLeft = yawThreshold
+ if (yaw > thresholdLeft && !hasShakeToLeft && hasShakeToRight) {
+ hasShakeToLeft = true
+ postProcess(state.toString() + "1")
+ }
+
+ if (hasShakeToLeft && hasShakeToRight) {
+ postProcess(state.toString() + "2")
+ gotoNextAction()
+ }
+ }
+ State.LIVENESS_CHECK_COMPLETED -> {
+
+ setViewMode(FaceRectView.DispState.ROUND_NORMAL)
+ setTitle( "Face Capture")
+ if(faceCaptured == 0) {
+ faceCaptured = 1
+ if(hasCoolDown) {
+ startTimer(timeoutFaceCapture)
+ }
+ }
+
+ val luminance = calculateLuminance(frame)
+ val faceInRect = isFaceInDetectionRect(face, frame.size.width, frame.size.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ setWarning("No face")
+ } else if(luminance < minimumLuminance) {
+ setWarning("Low luminance")
+ } else if(luminance > maximumLuminance) {
+ setWarning("High luminance")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ setWarning("Fit in circle")
+ } else if(!isFacingCamera(face)) {
+ setWarning("See front")
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ setWarning("Move closer")
+ } else if(isMouthOpened(face)) {
+ setWarning("Close mouth")
+ } else if(face!!.left_eye_closed > 0.5 || face!!.right_eye_closed > 0.5) {
+ setWarning("Do not blink")
+ } else {
+ setWarning("")
+ if(hasCoolDown == false) {
+ faceCaptured = 2
+ }
+ }
+
+ if(faceCaptured == 2) {
+ faceCaptured = 3
+ faceCapture(frame)
+ }
+ }
+ State.END -> {
+
+ }
+ }
+
+ if(adjustPreview(frame.size.width, frame.size.height))
+ sendMessage(HANDLE_UPDATE_FACE, faceResults)
+
+ }
+ }
+
+ private fun startCaptureTimer(timeOut: Int) {
+
+ timer?.cancel()
+ timer = object: CountDownTimer((timeOut * 1000).toLong() + 1000L, 1000) {
+ override fun onTick(millisUntilFinished: Long) {
+ runOnUiThread {
+ txtTimer?.text = (millisUntilFinished / 1000).toString() + "s"
+ progressTimer?.progress = (millisUntilFinished / 1000).toInt()
+ }
+ }
+
+ override fun onFinish() {
+ if(state == State.LIVENESS_CHECK_COMPLETED) {
+ faceCaptured = 2
+ } else {
+ endProcess("Liveness check timeout")
+ }
+ }
+ }
+ timer?.start()
+ }
+
+ private fun isFaceInDetectionRect(face: FaceBox?, frameWidth: Int, frameHeight: Int): ROI_CHECK_RESULT {
+ face ?: return ROI_CHECK_RESULT.ROI_NO_FACE
+ val maxSize = Math.max(frameWidth, frameHeight)
+ val minSize = Math.min(frameWidth, frameHeight)
+ var sizeRate = 0.45f
+ var interRate = 0.1f
+
+ val viewWidth = rectanglesView!!.width
+ val viewHeight = rectanglesView!!.height
+ val minView = Math.min(viewWidth, viewHeight)
+ val maxView = Math.max(viewWidth, viewHeight)
+ val ratioView = minView / maxView.toFloat()
+ val ratioFrame = minSize / maxSize.toFloat()
+
+ var cropRect = Rect()
+ if(state != State.ZOOM_IN && state != State.ZOOM_OUT && state != State.ZOOM_IN_OUT && state != State.ZOOM_OUT_IN) {
+ val margin = minView / 6
+ val rectHeight = (minView - 2 * margin) * 4 / 3
+ cropRect = Rect(margin.toInt(),
+ ((maxView - rectHeight) / 2).toInt(),
+ (minView - margin).toInt(),
+ ((maxView - rectHeight) / 2 + rectHeight).toInt()
+ )
+ } else if(state == State.ZOOM_OUT || (state == State.ZOOM_IN_OUT && hasZoomIn == true) || (state == State.ZOOM_OUT_IN && hasZoomOut == false)) {
+ val margin = minView / 4
+ val rectHeight = (minView - 2 * margin) * 4 / 3
+ cropRect = Rect(margin.toInt(),
+ ((maxView - rectHeight) / 2).toInt(),
+ (minView - margin).toInt(),
+ ((maxView - rectHeight) / 2 + rectHeight).toInt()
+ )
+ interRate = 0.01f
+ sizeRate = 0.50f
+ } else if(state == State.ZOOM_IN || (state == State.ZOOM_IN_OUT && hasZoomIn == false) || (state == State.ZOOM_OUT_IN && hasZoomOut == true)) {
+ val margin = minView / 15
+ val rectHeight = minView * 7 / 5
+ cropRect = Rect(margin.toInt(),
+ ((maxView - rectHeight) / 2).toInt(),
+ (minView - margin).toInt(),
+ ((maxView - rectHeight) / 2 + rectHeight).toInt()
+ )
+ sizeRate = 0.55f
+ }
+
+ var frameCropRect = Rect()
+ if(ratioView < ratioFrame) {
+ var dx = ((maxView * ratioFrame) - minView) / 2
+ var dy = 0f
+ var ratio = maxSize / maxView.toFloat()
+
+ val x1 = (cropRect.left + dx) * ratio
+ val y1 = (cropRect.top + dy) * ratio
+ val x2 = (cropRect.right + dx) * ratio
+ val y2 = (cropRect.bottom + dy) * ratio
+ frameCropRect = Rect(x1.toInt(), y1.toInt(), x2.toInt(), y2.toInt())
+ } else {
+ var dx = 0f
+ var dy = ((minView / ratioFrame) - maxView) / 2
+ var ratio = maxSize / maxView.toFloat()
+
+ val x1 = (cropRect.left + dx) * ratio
+ val y1 = (cropRect.top + dy) * ratio
+ val x2 = (cropRect.right + dx) * ratio
+ val y2 = (cropRect.bottom + dy) * ratio
+ frameCropRect = Rect(x1.toInt(), y1.toInt(), x2.toInt(), y2.toInt())
+ }
+
+ var faceLeft = Float.MAX_VALUE
+ var faceRight = 0f
+ var faceBottom = 0f
+ for(i in 0..67) {
+ faceLeft = Math.min(faceLeft, face.landmarks_68[i * 2])
+ faceRight = Math.max(faceRight, face.landmarks_68[i * 2])
+ faceBottom = Math.max(faceBottom, face.landmarks_68[i * 2 + 1])
+ }
+
+
+ val centerY = (face.y2 + face.y1) / 2
+ val topY = centerY - (face.y2 - face.y1) * 2 / 3
+
+ var interX = Math.max(0f, frameCropRect.left.toFloat() - faceLeft) + Math.max(0f, faceRight - frameCropRect.right.toFloat())
+ var interY = Math.max(0f, frameCropRect.top.toFloat() - topY) + Math.max(0f, faceBottom - frameCropRect.bottom.toFloat())
+
+ if(interX / frameCropRect.width().toFloat() > interRate || interY / frameCropRect.height().toFloat() > interRate) {
+ return ROI_CHECK_RESULT.ROI_INTERSECTS
+ }
+
+ if((face.y2 - face.y1) * (face.x2 - face.x1) < frameCropRect.width() * frameCropRect.height() * sizeRate) {
+ return ROI_CHECK_RESULT.ROI_SMALL_FACE
+ }
+
+ return ROI_CHECK_RESULT.ROI_FACE_OK
+ }
+
+ private fun isFacingCamera(face: FaceBox?): Boolean {
+ face ?: return false
+ return face.roll < rollThreshold && face.roll > -rollThreshold
+ && face.yaw < yawThreshold && face.yaw > -yawThreshold
+ && face.pitch < pitchThreshold && face.pitch > -pitchThreshold
+ }
+
+ private fun isEyeBlinking(face:FaceBox?): Boolean {
+ face ?: return false
+
+ if(lastEyeClosed == false) {
+ if(face.left_eye_closed > 0.5 && face.right_eye_closed > 0.5) {
+ lastEyeClosed = true
+ return false
+ }
+ } else {
+ if(face.left_eye_closed < 0.5 && face.right_eye_closed < 0.5) {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ private fun isMouthOpened(face: FaceBox?): Boolean {
+ face ?: return false
+ return face.mouth_opened > 0.5;
+ }
+
+ private fun lengthSquare(a: PointF, b: PointF): kotlin.Float {
+ val x = a.x - b.x
+ val y = a.y - b.y
+ return x * x + y * y
+ }
+
+ private fun setTitle(msg:String) {
+ sendMessage(HANDLE_SET_TITLE, msg)
+ }
+
+ private fun setViewMode(mode: FaceRectView.DispState) {
+ sendMessage(HANDLE_VIEW_MODE, mode)
+ }
+
+ private fun startTimer(timeout: Int) {
+ sendMessage(HANDLE_START_TIMER, timeout)
+ }
+
+ private fun setWarning(msg: String) {
+ warningMessage = msg
+ runOnUiThread { txtWarning.text = warningMessage }
+ }
+
+ private fun endProcess(msg: String) {
+ state = State.END
+ timer?.cancel()
+ sendMessage(HANDLE_TOAST_SHOW, msg)
+ finish()
+ }
+
+ private fun gotoNextAction() {
+
+ setWarning("")
+ sendMessage(HANDLE_STOP_TIMER, 0)
+ sendMessage(HANDLE_VIBRATOR, 0)
+ if(state == State.LIVENESS_CHECK_COMPLETED) {
+ lytPrepareFaceCapture.visibility = View.VISIBLE
+
+ val savePath = filesDir.path + "/capture.jpg"
+ val bitmapCapture = Utils.getCorrectlyOrientedImage(context, savePath)
+ imgFaceCapture.setImageBitmap(bitmapCapture)
+
+ val faceDetectionParam = FaceDetectionParam()
+ faceDetectionParam.check_face_occlusion = true
+ faceDetectionParam.check_eye_closeness = true
+ faceDetectionParam.check_mouth_opened = true
+ val faceResults = FaceSDK.faceDetection(bitmapCapture, faceDetectionParam)
+ var face: FaceBox? = null
+ if (faceResults != null && !faceResults.isEmpty()) {
+ face = faceResults[0]
+ }
+
+ val luminance = calcLuminanceFromBitmap(bitmapCapture)
+ val faceInRect = isFaceInDetectionRect(face, bitmapCapture.width, bitmapCapture.height)
+ if(faceInRect == ROI_CHECK_RESULT.ROI_NO_FACE) {
+ txtFaceCapture.text = "Face capture failed!"
+ txtFaceCaptureWarning.text = "No face"
+ } else if(luminance < minimumLuminance) {
+ txtFaceCapture.text = "Face capture failed!"
+ txtFaceCaptureWarning.text = "Low luminance"
+ } else if(luminance > maximumLuminance) {
+ txtFaceCapture.text = "Face capture failed!"
+ txtFaceCaptureWarning.text = "High luminance"
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_INTERSECTS) {
+ txtFaceCapture.text = "Face capture failed!"
+ txtFaceCaptureWarning.text = "Fit in circle"
+ } else if(!isFacingCamera(face)) {
+ txtFaceCapture.text = "Face capture failed!"
+ txtFaceCaptureWarning.text = "See front"
+ } else if(faceInRect == ROI_CHECK_RESULT.ROI_SMALL_FACE) {
+ txtFaceCapture.text = "Face capture failed!"
+ txtFaceCaptureWarning.text = "Move closer"
+ } else if(isMouthOpened(face)) {
+ txtFaceCapture.text = "Face capture failed!"
+ txtFaceCaptureWarning.text = "Close mouth"
+ } else if(face!!.left_eye_closed > 0.5 || face!!.right_eye_closed > 0.5) {
+ txtFaceCapture.text = "Face capture failed!"
+ txtFaceCaptureWarning.text = "Do not blink"
+ } else {
+ txtFaceCapture.text = "Face capture succeed!"
+ txtFaceCaptureWarning.text = ""
+
+ if(hasPostProcess) {
+ val thread = thread(start = true) {
+ // Code to run in the new thread
+ FileUploadRunnable(savePath).run()
+ }
+ }
+ }
+ state = State.END
+ } else if(currentActionIdx >= maxLivenessCount) {
+ state = State.LIVENESS_CHECK_COMPLETED
+ } else {
+ state = actionsList[actionsIdxs[currentActionIdx]]
+ }
+ currentActionIdx ++
+
+ if(state == State.LIVENESS_CHECK_COMPLETED) {
+ runOnUiThread {
+ Toast.makeText(
+ context,
+ "Liveness Check Succeed",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+
+ private fun Bitmap.flip(horizontal: Boolean, vertical: Boolean): Bitmap {
+ val matrix = Matrix()
+ matrix.preScale((if (horizontal) -1 else 1).toFloat(), (if (vertical) -1 else 1).toFloat())
+ return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
+ }
+
+ private fun Bitmap.rotate(degrees: Float): Bitmap {
+ val matrix = Matrix()
+ matrix.postRotate(degrees)
+ val scaledBitmap = Bitmap.createScaledBitmap(this, width, height, true)
+ return Bitmap.createBitmap(
+ scaledBitmap,
+ 0,
+ 0,
+ scaledBitmap.width,
+ scaledBitmap.height,
+ matrix,
+ true
+ )
+ }
+
+ private fun postProcess(saveName: String) {
+ if(hasPostProcess == false) {
+ return
+ }
+
+ val savePath = filesDir.path + "/" + saveName
+ val saveFile = File(savePath)
+ if(!saveFile.exists()) {
+ saveFile.createNewFile()
+ }
+
+ frontFotoapparat!!.takePicture().saveToFile(saveFile).whenAvailable {
+ val thread = thread(start = true) {
+ // Code to run in the new thread
+ FileUploadRunnable(savePath).run()
+ }
+ }
+ }
+
+ private fun faceCapture(frame: Frame) {
+ val savePath = filesDir.path + "/capture.jpg"
+ val saveFile = File(savePath)
+ if(!saveFile.exists()) {
+ saveFile.createNewFile()
+ }
+
+ YuvImage(
+ frame.image,
+ ImageFormat.NV21,
+ frame.size.width,
+ frame.size.height,
+ null
+ ).let { yuvImage ->
+ ByteArrayOutputStream().use { output ->
+ yuvImage.compressToJpeg(
+ Rect(0, 0, frame.size.width, frame.size.height),
+ 100,
+ output
+ )
+ output.toByteArray().apply {
+ BitmapFactory.decodeByteArray(this, 0, size)?.let { bitmap ->
+
+ val fixedBitmap = bitmap.rotate(90f).flip(
+ false, true
+ )
+
+ try {
+ val fos = FileOutputStream(saveFile)
+ fixedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
+ fos.flush()
+ fos.close()
+
+ runOnUiThread {
+ frontFotoapparat!!.stop()
+ gotoNextAction()
+ if(hasPostProcess) {
+ val thread = thread(start = true) {
+ // Code to run in the new thread
+ FileUploadRunnable(savePath).run()
+ }
+ }
+ }
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+ }
+ }
+ }
+ }
+
+ inner class FileUploadRunnable(saveName_: String) : Runnable {
+ val saveName: String
+
+ init {
+ saveName = saveName_
+ }
+
+ override fun run() {
+// try {
+// // Load the certificate file
+// val inputStream: InputStream = resources.openRawResource(R.raw.mycert)
+// val certificateFactory = CertificateFactory.getInstance("X.509")
+// val serverCert = certificateFactory.generateCertificate(inputStream) as X509Certificate
+//
+// val trustStore = KeyStore.getInstance(KeyStore.getDefaultType())
+// trustStore.load(null, null)
+// trustStore.setCertificateEntry("server", serverCert)
+//
+// val trustManagerFactory = TrustManagerFactory.getInstance(
+// TrustManagerFactory.getDefaultAlgorithm()
+// )
+// trustManagerFactory.init(trustStore)
+//
+// val sslContext = SSLContext.getInstance("TLS")
+// sslContext.init(null, trustManagerFactory.trustManagers, SecureRandom())
+//
+// val client = OkHttpClient.Builder()
+// .addInterceptor(HttpLoggingInterceptor().apply {
+// level = HttpLoggingInterceptor.Level.BODY
+// })
+// .sslSocketFactory(sslContext.socketFactory, trustManagerFactory.trustManagers[0] as X509TrustManager)
+// .build()
+//
+// val file = File(saveName)
+// val requestBody: RequestBody = MultipartBody.Builder()
+// .setType(MultipartBody.FORM)
+// .addFormDataPart(
+// "image",
+// file.name,
+// RequestBody.create("multipart/form-data".toMediaTypeOrNull(), file)
+// )
+// .build()
+//
+// val request: Request = Builder()
+// .url(postProcessAddress)
+// .post(requestBody)
+// .build()
+//
+// val response = client.newCall(request).execute()
+// Log.e("TestEngine", "response: " + response)
+// } catch(e:Exception) {
+// e.printStackTrace()
+// }
+ }
+ }
+
+ fun calculateLuminance(frame: Frame): Double {
+ // Get the Y (luminance) plane
+ val yBuffer = frame.image
+ val yRowStride = frame.size.width
+ val yPixelStride = 1
+
+ val width = frame.size.width
+ val height = frame.size.height
+
+ var totalLuminance = 0.0
+ var pixelCount = 0
+
+ for (row in 0 until height) {
+ for (col in 0 until width step yPixelStride) {
+ val index = row * yRowStride + col
+ val yValue = yBuffer.get(index).toInt() and 0xFF // Convert byte to unsigned int
+ totalLuminance += yValue
+ pixelCount++
+ }
+ }
+
+ // Calculate the average luminance
+ return if (pixelCount > 0) totalLuminance / pixelCount else 0.0
+ }
+
+ fun calcLuminanceFromBitmap(bitmap: Bitmap): Int {
+ val width = bitmap.width
+ val height = bitmap.height
+
+ // Convert the Bitmap to ARGB pixels
+ val pixels = IntArray(width * height)
+ bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
+
+ var sum = 0.0
+
+ // Iterate through each pixel and calculate luminance
+ for (pixel in pixels) {
+ val R = (pixel shr 16) and 0xFF // Extract red channel
+ val G = (pixel shr 8) and 0xFF // Extract green channel
+ val B = pixel and 0xFF // Extract blue channel
+
+ // Calculate Y (luminance) using the same formula
+ val Y = ((66 * R + 129 * G + 25 * B + 128) shr 8) + 16
+ sum += Y
+ }
+
+ // Return the average luminance
+ return (sum / (width * height)).toInt()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbyai/faceattribute/CaptureActivityKt.kt b/app/src/main/java/com/kbyai/faceattribute/CaptureActivityKt.kt
new file mode 100644
index 0000000..68ef590
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/CaptureActivityKt.kt
@@ -0,0 +1,318 @@
+package com.kbyai.faceattribute
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.os.Bundle
+import android.util.Size
+import android.view.View
+import android.widget.TextView
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.camera.core.CameraSelector
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.preference.PreferenceManager
+
+import com.kbyai.facesdk.FaceBox
+import com.kbyai.facesdk.FaceDetectionParam
+import com.kbyai.facesdk.FaceSDK
+import io.fotoapparat.Fotoapparat
+import io.fotoapparat.parameter.Resolution
+import io.fotoapparat.preview.Frame
+import io.fotoapparat.preview.FrameProcessor
+import io.fotoapparat.selector.front
+import io.fotoapparat.view.CameraView
+import java.util.Random
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+
+class CaptureActivityKt : AppCompatActivity(), CaptureView.ViewModeChanged {
+
+ val TAG = CaptureActivityKt::class.java.simpleName
+ val PREVIEW_WIDTH = 720
+ val PREVIEW_HEIGHT = 1280
+
+ private lateinit var fotoapparat: Fotoapparat
+ private lateinit var context: Context
+
+ private lateinit var cameraView: CameraView
+
+ private lateinit var captureView: CaptureView
+
+ private lateinit var warningTxt: TextView
+
+ private lateinit var livenessTxt: TextView
+
+ private lateinit var qualityTxt: TextView
+
+ private lateinit var luminaceTxt: TextView
+
+ private lateinit var lytCaptureResult: ConstraintLayout
+
+ private var capturedBitmap: Bitmap? = null
+
+ private var capturedFace: FaceBox? = null
+
+ private var yawThreshold = 0.0f
+ private var rollThreshold = 0.0f
+ private var pitchThreshold = 0.0f
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_capture_kt)
+
+ context = this
+ cameraView = 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)
+
+ captureView.setViewModeInterface(this)
+ captureView.setViewMode(CaptureView.VIEW_MODE.NO_FACE_PREPARE)
+
+ val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
+ yawThreshold = sharedPreferences.getString("valid_yaw_angle", "" + SettingsActivity.DEFAULT_VALID_YAW_ANGLE)!!.toFloat()
+ rollThreshold = sharedPreferences.getString("valid_roll_angle", "" + SettingsActivity.DEFAULT_VALID_ROLL_ANGLE)!!.toFloat()
+ pitchThreshold = sharedPreferences.getString("valid_pitch_angle", "" + SettingsActivity.DEFAULT_VALID_PITCH_ANGLE)!!.toFloat()
+
+ findViewById(R.id.buttonEnroll).setOnClickListener {
+ val faceImage = Utils.cropFace(capturedBitmap, capturedFace)
+ val templates = FaceSDK.templateExtraction(capturedBitmap, capturedFace)
+
+// val dbManager = DBManager(context)
+// val min = 10000
+// val max = 20000
+// val random = 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()
+ }
+
+ fotoapparat = Fotoapparat.with(this)
+ .into(cameraView)
+ .lensPosition(front())
+ .frameProcessor(FaceFrameProcessor())
+ .previewResolution { Resolution(PREVIEW_HEIGHT,PREVIEW_WIDTH) }
+ .build()
+
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_DENIED
+ ) {
+ ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 1)
+ } else {
+ fotoapparat.start()
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_GRANTED
+ ) {
+ fotoapparat.start()
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ fotoapparat.stop()
+ captureView.setFaceBoxes(null)
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == 1) {
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_GRANTED
+ ) {
+ fotoapparat.start()
+ }
+ }
+ }
+
+ override fun view5_finished() {
+ val param = FaceDetectionParam()
+ param.check_liveness = true
+ param.check_liveness_level = 0
+
+ val faceBoxes = FaceSDK.faceDetection(capturedBitmap, param)
+ if (faceBoxes != null && faceBoxes.size > 0) {
+ if (faceBoxes[0].liveness > 0.7) {
+ val msg = String.format("Liveness: Real, score = %.03f", faceBoxes[0].liveness)
+ livenessTxt.text = msg
+ } else {
+ val msg = String.format("Liveness: Spoof, score = %.03f", faceBoxes[0].liveness)
+ livenessTxt.text = msg
+ }
+ }
+
+ if (capturedFace!!.face_quality < 0.5f) {
+ val msg = String.format("Quality: Low, score = %.03f", capturedFace!!.face_quality)
+ qualityTxt.text = msg
+ } else if (capturedFace!!.face_quality < 0.75f) {
+ val msg = String.format("Quality: Medium, score = %.03f", capturedFace!!.face_quality)
+ qualityTxt.text = msg
+ } else {
+ val msg = String.format("Quality: High, score = %.03f", capturedFace!!.face_quality)
+ qualityTxt.text = msg
+ }
+
+ val msg = String.format("Luminance: %.03f", capturedFace!!.face_luminance)
+ luminaceTxt.text = msg
+
+ lytCaptureResult.visibility = View.VISIBLE
+ }
+
+ fun checkFace(faceBoxes: List?, context: Context?): FACE_CAPTURE_STATE {
+ if (faceBoxes == null || faceBoxes.size == 0) return FACE_CAPTURE_STATE.NO_FACE
+
+ if (faceBoxes.size > 1) {
+ return FACE_CAPTURE_STATE.MULTIPLE_FACES
+ }
+
+ val faceBox = faceBoxes[0]
+ var faceLeft = Float.MAX_VALUE
+ var faceRight = 0f
+ var faceBottom = 0f
+ for (i in 0..67) {
+ faceLeft = min(faceLeft.toDouble(), faceBox.landmarks_68[i * 2].toDouble()).toFloat()
+ faceRight =
+ max(faceRight.toDouble(), faceBox.landmarks_68[i * 2].toDouble()).toFloat()
+ faceBottom =
+ max(faceBottom.toDouble(), faceBox.landmarks_68[i * 2 + 1].toDouble()).toFloat()
+ }
+
+ val sizeRate = 0.30f
+ val interRate = 0.03f
+ val frameSize = Size(PREVIEW_WIDTH, PREVIEW_HEIGHT)
+ val roiRect = CaptureView.getROIRect(frameSize)
+ val centerY = ((faceBox.y2 + faceBox.y1) / 2).toFloat()
+ val topY = centerY - (faceBox.y2 - faceBox.y1) * 2 / 3
+ val interX =
+ (max(0.0, (roiRect.left - faceLeft).toDouble()) + max(
+ 0.0,
+ (faceRight - roiRect.right).toDouble()
+ )).toFloat()
+ val interY =
+ (max(0.0, (roiRect.top - topY).toDouble()) + max(
+ 0.0,
+ (faceBottom - roiRect.bottom).toDouble()
+ )).toFloat()
+ 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 (abs(faceBox.yaw.toDouble()) > yawThreshold || abs(
+ faceBox.roll.toDouble()
+ ) > rollThreshold || abs(faceBox.pitch.toDouble()) > pitchThreshold
+ ) {
+ return FACE_CAPTURE_STATE.NO_FRONT
+ }
+
+ if (faceBox.face_occlusion > 0.5) {
+ return FACE_CAPTURE_STATE.FACE_OCCLUDED
+ }
+
+ if (faceBox.left_eye_closed > 0.5 ||
+ faceBox.right_eye_closed > 0.5
+ ) {
+ return FACE_CAPTURE_STATE.EYE_CLOSED
+ }
+
+ if (faceBox.mouth_opened > 0.5) {
+ return FACE_CAPTURE_STATE.MOUTH_OPENED
+ }
+
+ return FACE_CAPTURE_STATE.CAPTURE_OK
+ }
+
+ inner class FaceFrameProcessor : FrameProcessor {
+
+ override fun process(frame: Frame) {
+
+ if(captureView.viewMode == CaptureView.VIEW_MODE.NO_FACE_PREPARE) {
+ return
+ }
+
+ var cameraMode = 7
+ val bitmap = FaceSDK.yuv2Bitmap(frame.image, frame.size.width, frame.size.height, cameraMode)
+
+ val faceDetectionParam = FaceDetectionParam()
+ faceDetectionParam.check_face_occlusion = true
+ faceDetectionParam.check_eye_closeness = true
+ faceDetectionParam.check_mouth_opened = true
+ val faceBoxes = FaceSDK.faceDetection(bitmap, faceDetectionParam)
+
+ val faceCaptureState = checkFace(faceBoxes, context)
+
+ if (captureView.viewMode == CaptureView.VIEW_MODE.REPEAT_NO_FACE_PREPARE) {
+ if (faceCaptureState.compareTo(FACE_CAPTURE_STATE.NO_FACE) > 0) {
+ runOnUiThread { captureView.setViewMode(CaptureView.VIEW_MODE.TO_FACE_CIRCLE) }
+ }
+ } else if (captureView.viewMode == CaptureView.VIEW_MODE.FACE_CIRCLE) {
+ runOnUiThread {
+ captureView.setFrameSize(Size(bitmap.width, bitmap.height))
+ captureView.setFaceBoxes(faceBoxes)
+ if (faceCaptureState == FACE_CAPTURE_STATE.NO_FACE) {
+ warningTxt.text = ""
+
+ captureView.setViewMode(CaptureView.VIEW_MODE.FACE_CIRCLE_TO_NO_FACE)
+ } else if (faceCaptureState == FACE_CAPTURE_STATE.MULTIPLE_FACES) warningTxt.text =
+ "Multiple face detected!"
+ else if (faceCaptureState == FACE_CAPTURE_STATE.FIT_IN_CIRCLE) warningTxt.text =
+ "Fit in circle!"
+ else if (faceCaptureState == FACE_CAPTURE_STATE.MOVE_CLOSER) warningTxt.text =
+ "Move closer!"
+ else if (faceCaptureState == FACE_CAPTURE_STATE.NO_FRONT) warningTxt.text =
+ "Not fronted face!"
+ else if (faceCaptureState == FACE_CAPTURE_STATE.FACE_OCCLUDED) warningTxt.text =
+ "Face occluded!"
+ else if (faceCaptureState == FACE_CAPTURE_STATE.EYE_CLOSED) warningTxt.text =
+ "Eye closed!"
+ else if (faceCaptureState == FACE_CAPTURE_STATE.MOUTH_OPENED) warningTxt.text =
+ "Mouth opened!"
+ else if (faceCaptureState == FACE_CAPTURE_STATE.SPOOFED_FACE) warningTxt.text =
+ "Spoof face"
+ else {
+ warningTxt.text = ""
+ captureView.setViewMode(CaptureView.VIEW_MODE.FACE_CAPTURE_PREPARE)
+
+ capturedBitmap = bitmap
+ capturedFace = faceBoxes[0]
+ captureView.setCapturedBitmap(capturedBitmap)
+ }
+ }
+ } else if (captureView.viewMode == CaptureView.VIEW_MODE.FACE_CAPTURE_PREPARE) {
+ if (faceCaptureState == FACE_CAPTURE_STATE.CAPTURE_OK) {
+ if (faceBoxes[0].face_quality > capturedFace!!.face_quality) {
+ capturedBitmap = bitmap
+ capturedFace = faceBoxes[0]
+ captureView.setCapturedBitmap(capturedBitmap)
+ }
+ }
+ } else if (captureView.viewMode == CaptureView.VIEW_MODE.FACE_CAPTURE_DONE) {
+ runOnUiThread { fotoapparat.stop() }
+ }
+ }
+ }
+}
\ 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/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/FaceRectTransformer.java b/app/src/main/java/com/kbyai/faceattribute/FaceRectTransformer.java
new file mode 100644
index 0000000..2190625
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/FaceRectTransformer.java
@@ -0,0 +1,205 @@
+package com.kbyai.faceattribute;
+
+import android.graphics.Rect;
+import android.util.Log;
+
+
+public class FaceRectTransformer {
+ private int previewWidth, previewHeight, canvasWidth, canvasHeight, cameraDisplayOrientation;
+ private boolean isMirror;
+ private boolean mirrorHorizontal = false, mirrorVertical = false;
+ private int cameraId;
+
+ public FaceRectTransformer(int previewWidth, int previewHeight, int canvasWidth,
+ int canvasHeight, int cameraDisplayOrientation, int cameraId,
+ boolean isMirror, boolean mirrorHorizontal, boolean mirrorVertical) {
+ this.previewWidth = previewWidth;
+ this.previewHeight = previewHeight;
+ this.canvasWidth = canvasWidth;
+ this.canvasHeight = canvasHeight;
+ this.cameraDisplayOrientation = cameraDisplayOrientation;
+ this.cameraId = cameraId;
+ this.isMirror = isMirror;
+ this.mirrorHorizontal = mirrorHorizontal;
+ this.mirrorVertical = mirrorVertical;
+ }
+
+ public Rect adjustRect(Rect ftRect) {
+ int previewWidth = this.previewWidth;
+ int previewHeight = this.previewHeight;
+ int canvasWidth = this.canvasWidth;
+ int canvasHeight = this.canvasHeight;
+ int cameraDisplayOrientation = this.cameraDisplayOrientation;
+ int cameraId = this.cameraId;
+ boolean isMirror = this.isMirror;
+ boolean mirrorHorizontal = this.mirrorHorizontal;
+ boolean mirrorVertical = this.mirrorVertical;
+
+ if (ftRect == null) {
+ return null;
+ }
+
+ Rect rect = new Rect(ftRect);
+ float horizontalRatio;
+ float verticalRatio;
+ if (cameraDisplayOrientation % 180 == 0) {
+ horizontalRatio = (float) canvasWidth / (float) previewWidth;
+ verticalRatio = (float) canvasHeight / (float) previewHeight;
+
+ } else {
+ horizontalRatio = (float) canvasHeight / (float) previewWidth;
+ verticalRatio = (float) canvasWidth / (float) previewHeight;
+
+ }
+ rect.left *= horizontalRatio;
+ rect.right *= horizontalRatio;
+ rect.top *= verticalRatio;
+ rect.bottom *= verticalRatio;
+
+ Rect newRect = new Rect();
+ switch (cameraDisplayOrientation) {
+ case 0:
+ if (cameraId == 0) {
+ newRect.left = canvasWidth - rect.right;
+ newRect.right = canvasWidth - rect.left;
+ } else {
+ newRect.left = rect.left;
+ newRect.right = rect.right;
+ }
+ newRect.top = rect.top;
+ newRect.bottom = rect.bottom;
+ break;
+ case 90:
+ newRect.right = canvasWidth - rect.top;
+ newRect.left = canvasWidth - rect.bottom;
+ if (cameraId == 0) {
+ newRect.top = canvasHeight - rect.right;
+ newRect.bottom = canvasHeight - rect.left;
+ } else {
+ newRect.top = rect.left;
+ newRect.bottom = rect.right;
+ }
+ break;
+ case 180:
+ newRect.top = canvasHeight - rect.bottom;
+ newRect.bottom = canvasHeight - rect.top;
+ if (cameraId == 0) {
+ newRect.left = rect.left;
+ newRect.right = rect.right;
+ } else {
+ newRect.left = canvasWidth - rect.right;
+ newRect.right = canvasWidth - rect.left;
+ }
+ break;
+ case 270:
+ newRect.left = rect.top;
+ newRect.right = rect.bottom;
+ if (cameraId == 0) {
+ newRect.top = rect.left;
+ newRect.bottom = rect.right;
+ } else {
+ newRect.top = canvasHeight - rect.right;
+ newRect.bottom = canvasHeight - rect.left;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /**
+ * isMirror mirrorHorizontal finalIsMirrorHorizontal
+ * true true false
+ * false false false
+ * true false true
+ * false true true
+ *
+ * XOR
+ */
+ if (isMirror ^ mirrorHorizontal) {
+ int left = newRect.left;
+ int right = newRect.right;
+ newRect.left = canvasWidth - right;
+ newRect.right = canvasWidth - left;
+ }
+ if (mirrorVertical) {
+ int top = newRect.top;
+ int bottom = newRect.bottom;
+ newRect.top = canvasHeight - bottom;
+ newRect.bottom = canvasHeight - top;
+ }
+
+ return newRect;
+ }
+
+ public void setPreviewWidth(int previewWidth) {
+ this.previewWidth = previewWidth;
+ }
+
+ public void setPreviewHeight(int previewHeight) {
+ this.previewHeight = previewHeight;
+ }
+
+ public void setCanvasWidth(int canvasWidth) {
+ this.canvasWidth = canvasWidth;
+ }
+
+ public void setCanvasHeight(int canvasHeight) {
+ this.canvasHeight = canvasHeight;
+ }
+
+ public void setCameraDisplayOrientation(int cameraDisplayOrientation) {
+ this.cameraDisplayOrientation = cameraDisplayOrientation;
+ }
+
+ public void setCameraId(int cameraId) {
+ this.cameraId = cameraId;
+ }
+
+ public void setMirror(boolean mirror) {
+ isMirror = mirror;
+ }
+
+ public int getPreviewWidth() {
+ return previewWidth;
+ }
+
+ public int getPreviewHeight() {
+ return previewHeight;
+ }
+
+ public int getCanvasWidth() {
+ return canvasWidth;
+ }
+
+ public int getCanvasHeight() {
+ return canvasHeight;
+ }
+
+ public int getCameraDisplayOrientation() {
+ return cameraDisplayOrientation;
+ }
+
+ public int getCameraId() {
+ return cameraId;
+ }
+
+ public boolean isMirror() {
+ return isMirror;
+ }
+
+ public boolean isMirrorHorizontal() {
+ return mirrorHorizontal;
+ }
+
+ public void setMirrorHorizontal(boolean mirrorHorizontal) {
+ this.mirrorHorizontal = mirrorHorizontal;
+ }
+
+ public boolean isMirrorVertical() {
+ return mirrorVertical;
+ }
+
+ public void setMirrorVertical(boolean mirrorVertical) {
+ this.mirrorVertical = mirrorVertical;
+ }
+}
diff --git a/app/src/main/java/com/kbyai/faceattribute/FaceRectView.java b/app/src/main/java/com/kbyai/faceattribute/FaceRectView.java
new file mode 100644
index 0000000..7058b31
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/FaceRectView.java
@@ -0,0 +1,181 @@
+package com.kbyai.faceattribute;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+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.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+
+public class FaceRectView extends View {
+
+ public enum DispState {
+ NO_FACE,
+ FACE_DETECTED,
+ ROUND_NORMAL,
+ ROUND_ZOOM_IN,
+ ROUND_ZOOM_OUT
+ };
+
+ private Paint paint;
+ private static final int DEFAULT_FACE_RECT_THICKNESS = 6;
+
+ private Paint scrimPaint;
+ private Paint noFaceScrimPaint;
+ private Paint outSideScrimPaint;
+ private Paint eraserPaint;
+ private Paint boxPaint;
+ private int mShader = 0;
+ private DispState mMode = DispState.NO_FACE;
+
+ private boolean hasFace = false;
+
+ @ColorInt
+ private int boxGradientStartColor;
+ @ColorInt
+ private int boxGradientEndColor;
+
+ Context mContext;
+
+ public FaceRectView(Context context) {
+ this(context, null);
+
+ mContext = context;
+ init();
+ }
+
+ public FaceRectView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ paint = new Paint();
+
+ init();
+ }
+
+ public void setHasFace(boolean hasFace) {
+ this.hasFace = hasFace;
+ }
+
+ public void setMode(DispState mode) {
+ mMode = mode;
+ postInvalidate();
+ }
+
+ public void init() {
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
+ scrimPaint = new Paint();
+ noFaceScrimPaint = new Paint();
+ outSideScrimPaint = new Paint();
+ // Sets up a gradient background color at vertical.
+
+ eraserPaint = new Paint();
+ eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+
+ boxPaint = new Paint();
+ boxPaint.setStyle(Paint.Style.STROKE);
+ boxPaint.setStrokeWidth(1);
+ boxPaint.setColor(Color.WHITE);
+
+ boxGradientStartColor = mContext.getColor(R.color.bounding_box_gradient_start);
+ boxGradientEndColor = mContext.getColor(R.color.md_theme_light_onPrimary);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if(getWidth() > 0 && mShader == 0) {
+ mShader = 1;
+ scrimPaint.setShader(
+ new LinearGradient(
+ 0,
+ 0,
+ getWidth(),
+ getHeight(),
+ mContext.getColor(R.color.object_confirmed_bg_gradient_start),
+ mContext.getColor(R.color.object_confirmed_bg_gradient_end),
+ Shader.TileMode.CLAMP));
+
+ noFaceScrimPaint.setShader(
+ new LinearGradient(
+ 0,
+ 0,
+ getWidth(),
+ getHeight(),
+ mContext.getColor(R.color.bg_gradient_noface_start),
+ mContext.getColor(R.color.bg_gradient_noface_end),
+ Shader.TileMode.CLAMP));
+ }
+
+ RectF rect = new RectF();
+ if(mMode == DispState.ROUND_NORMAL) {
+ int margin = getWidth() / 6;
+ int rectHeight = (getWidth() - 2 * margin) * 4 / 3;
+ canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), scrimPaint);
+
+ rect = new RectF(margin, (getHeight() - rectHeight) / 2, getWidth() - margin, (getHeight() - rectHeight) / 2 + rectHeight);
+ canvas.drawRoundRect(rect, rect.width() / 2, rect.height() / 2, eraserPaint);
+ } else if(mMode == DispState.ROUND_ZOOM_OUT) { //zoom in
+ int margin = getWidth() / 4;
+ int rectHeight = (getWidth() - 2 * margin) * 4 / 3;
+ canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), scrimPaint);
+
+ rect = new RectF(margin, (getHeight() - rectHeight) / 2, getWidth() - margin, (getHeight() - rectHeight) / 2 + rectHeight);
+ canvas.drawRoundRect(rect, rect.width() / 2, rect.height() / 2, eraserPaint);
+ } else if(mMode == DispState.ROUND_ZOOM_IN) { //zoom out
+ int margin = getWidth() / 15;
+ int rectHeight = getWidth() * 7 / 5;
+ canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), scrimPaint);
+
+ rect = new RectF(margin, (getHeight() - rectHeight) / 2, getWidth() - margin, (getHeight() - rectHeight) / 2 + rectHeight);
+ canvas.drawRoundRect(rect, rect.width() / 2, rect.height() / 2, eraserPaint);
+ } else if(mMode == DispState.FACE_DETECTED || mMode == DispState.NO_FACE) {
+ int margin = getWidth() / 6;
+ int rectHeight = (getWidth() - 2 * margin) * 4 / 3;
+ canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), noFaceScrimPaint);
+
+ rect = new RectF(margin, (getHeight() - rectHeight) / 2, getWidth() - margin, (getHeight() - rectHeight) / 2 + rectHeight);
+
+ if(hasFace) {
+ outSideScrimPaint.setStyle(Paint.Style.STROKE);
+ outSideScrimPaint.setStrokeWidth(30);
+ outSideScrimPaint.setColor(Color.GREEN);
+ outSideScrimPaint.setAntiAlias(true);
+
+ canvas.drawRoundRect(rect, 50, 50, outSideScrimPaint);
+ }
+ canvas.drawRoundRect(rect, 50, 50, eraserPaint);
+ }
+
+ // Draws the bounding box with a gradient border color at vertical.
+ if(mMode != DispState.NO_FACE) {
+ boxPaint.setShader(
+ new LinearGradient(
+ rect.left,
+ rect.top,
+ rect.left,
+ rect.bottom,
+ boxGradientStartColor,
+ boxGradientEndColor,
+ Shader.TileMode.CLAMP));
+ canvas.drawRoundRect(rect, rect.width() / 2, rect.height() / 2, boxPaint);
+ }
+ }
+
+}
\ No newline at end of file
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..705e1c0
--- /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 < 0.7)
+ {
+ 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/FloatEditTextPreference.kt b/app/src/main/java/com/kbyai/faceattribute/FloatEditTextPreference.kt
new file mode 100644
index 0000000..74b29a9
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/FloatEditTextPreference.kt
@@ -0,0 +1,18 @@
+package com.ttv.facerecog
+
+import android.content.Context
+import android.text.InputType
+import android.util.AttributeSet
+import androidx.preference.EditTextPreference
+
+class FloatEditTextPreference(context: Context?, attrs: AttributeSet?) :
+ EditTextPreference(context!!, attrs) {
+
+ override fun getPersistedString(defaultReturnValue: String?): String? {
+ return getPersistedFloat(0.0f).toString()
+ }
+
+ override fun persistString(value: String?): Boolean {
+ return persistFloat(value!!.toFloat())
+ }
+}
diff --git a/app/src/main/java/com/kbyai/faceattribute/HttpPostMultipart.java b/app/src/main/java/com/kbyai/faceattribute/HttpPostMultipart.java
new file mode 100644
index 0000000..c0e4bea
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/HttpPostMultipart.java
@@ -0,0 +1,128 @@
+package com.kbyai.faceattribute;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.UUID;
+
+public class HttpPostMultipart {
+ private final String boundary;
+ private static final String LINE = "\r\n";
+ private HttpURLConnection httpConn;
+ private String charset;
+ private OutputStream outputStream;
+ private PrintWriter writer;
+
+ /**
+ * This constructor initializes a new HTTP POST request with content type
+ * is set to multipart/form-data
+ *
+ * @param requestURL
+ * @param charset
+ * @param headers
+ * @throws IOException
+ */
+ public HttpPostMultipart(String requestURL, String charset, Map headers) throws IOException {
+ this.charset = charset;
+ boundary = UUID.randomUUID().toString();
+ URL url = new URL(requestURL);
+ httpConn = (HttpURLConnection) url.openConnection();
+ httpConn.setUseCaches(false);
+ httpConn.setDoOutput(true); // indicates POST method
+ httpConn.setDoInput(true);
+ httpConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
+ if (headers != null && headers.size() > 0) {
+ Iterator it = headers.keySet().iterator();
+ while (it.hasNext()) {
+ String key = it.next();
+ String value = headers.get(key);
+ httpConn.setRequestProperty(key, value);
+ }
+ }
+ outputStream = httpConn.getOutputStream();
+ writer = new PrintWriter(new OutputStreamWriter(outputStream, charset), true);
+ }
+
+ /**
+ * Adds a form field to the request
+ *
+ * @param name field name
+ * @param value field value
+ */
+ public void addFormField(String name, String value) {
+ writer.append("--" + boundary).append(LINE);
+ writer.append("Content-Disposition: form-data; name=\"" + name + "\"").append(LINE);
+ writer.append("Content-Type: text/plain; charset=" + charset).append(LINE);
+ writer.append(LINE);
+ writer.append(value).append(LINE);
+ writer.flush();
+ }
+
+ /**
+ * Adds a upload file section to the request
+ *
+ * @param fieldName
+ * @param uploadFile
+ * @throws IOException
+ */
+ public void addFilePart(String fieldName, File uploadFile)
+ throws IOException {
+ String fileName = uploadFile.getName();
+ writer.append("--" + boundary).append(LINE);
+ writer.append("Content-Disposition: form-data; name=\"" + fieldName + "\"; filename=\"" + fileName + "\"").append(LINE);
+ writer.append("Content-Type: " + URLConnection.guessContentTypeFromName(fileName)).append(LINE);
+ writer.append("Content-Transfer-Encoding: binary").append(LINE);
+ writer.append(LINE);
+ writer.flush();
+
+ FileInputStream inputStream = new FileInputStream(uploadFile);
+ byte[] buffer = new byte[4096];
+ int bytesRead = -1;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, bytesRead);
+ }
+ outputStream.flush();
+ inputStream.close();
+ writer.append(LINE);
+ writer.flush();
+ }
+
+ /**
+ * Completes the request and receives response from the server.
+ *
+ * @return String as response in case the server returned
+ * status OK, otherwise an exception is thrown.
+ * @throws IOException
+ */
+ public String finish() throws IOException {
+ String response = "";
+ writer.flush();
+ writer.append("--" + boundary + "--").append(LINE);
+ writer.close();
+
+ // checks server's status code first
+ int status = httpConn.getResponseCode();
+ if (status == HttpURLConnection.HTTP_OK) {
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int length;
+ while ((length = httpConn.getInputStream().read(buffer)) != -1) {
+ result.write(buffer, 0, length);
+ }
+ response = result.toString(this.charset);
+ httpConn.disconnect();
+ } else {
+ throw new IOException("Server returned non-OK status: " + status);
+ }
+ return response;
+ }
+}
\ No newline at end of file
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..6b2a5cd
--- /dev/null
+++ b/app/src/main/java/com/kbyai/faceattribute/MainActivity.kt
@@ -0,0 +1,93 @@
+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 com.kbyai.faceattribute.CameraActivity
+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 textWarning: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ textWarning = findViewById(R.id.textWarning)
+
+
+ var ret = FaceSDK.setActivation(
+ "DYv3mu71v8b9hqraZETNpg+CdriRbg1qLLMbfNqTeGdvSdbGPGudJpiVR4Tl9TEcJrZrZG+59ay6\n" +
+ "BtL78C1VsDGsOzSKw9ssETgVnT9DIc/LdNrqs4/o7o3nO0ZPz3iNu/P2jKkUXLo/uzh+aaVLbi55\n" +
+ "X9NQYhD5EHFqL2mLtGxcfqccTHLMW0MIe0Wq65hzPIrR6oh7tvtKzX5EcOOPv8UK2a3i9+MgtG4Y\n" +
+ "b+CHoQ0lNJhmZkpdKmcRidibJLKgwJqDPiZfwsW6C3hcrNNo6T8T+NMZ4W7rHcQKfdSr0yXtYqCr\n" +
+ "kaMKrGzlk8nYubwfqZGeAKSyjuL8mWWgY57I3Q=="
+ )
+
+ 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!")
+ }
+ }
+
+ findViewById