diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..d2b70a8
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+PiDroid
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..61a9130
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..a5f05cd
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..d5d35ec
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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..309a5d5
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,57 @@
+plugins {
+ id 'com.android.application'
+ id 'kotlin-android'
+}
+
+android {
+ signingConfigs {
+ pidroid {
+ keyAlias 'androiddebugkey'
+ keyPassword 'android'
+ storeFile file('/home/adrian/.android/debug.keystore')
+ storePassword 'android'
+ }
+ }
+ compileSdkVersion 30
+ buildToolsVersion "30.0.3"
+
+ defaultConfig {
+ applicationId "com.suaro.pidroidapp"
+ minSdkVersion 23
+ targetSdkVersion 30
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-build.pro'
+ signingConfig signingConfigs.pidroid
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+}
+
+dependencies {
+
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation 'androidx.core:core-ktx:1.3.2'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'com.google.android.material:material:1.2.1'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+ implementation project(path: ':pidroid')
+ testImplementation 'junit:junit:4.+'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+}
\ 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/suaro/pidroid/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/suaro/pidroid/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..f67ac82
--- /dev/null
+++ b/app/src/androidTest/java/com/suaro/pidroid/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.suaro.pidroidapp
+
+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.suaro.pidroidapp", 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..ec6eee2
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/ImageActivity.kt b/app/src/main/java/com/suaro/pidroidapp/ImageActivity.kt
new file mode 100644
index 0000000..912f874
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/ImageActivity.kt
@@ -0,0 +1,94 @@
+package com.suaro.pidroidapp
+
+import android.content.res.AssetManager
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.os.Bundle
+import android.os.Handler
+import android.util.Log
+import android.widget.FrameLayout
+import android.widget.ImageView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.FragmentTransaction
+import com.suaro.pidroid.Pidroid
+import com.suaro.pidroid.core.PidroidConfig
+import com.suaro.pidroidapp.camera.Camera
+import com.suaro.pidroidapp.capturer.Capturer
+import com.suaro.pidroidapp.capturer.view.CapturerFragment
+import com.suaro.pidroidapp.core.CameraPreviewListener
+import java.util.*
+
+
+class ImageActivity : AppCompatActivity(), Camera.View, Capturer.View{
+
+ private lateinit var capturerLayout: FrameLayout
+ private lateinit var imageTest: ImageView
+
+ private lateinit var capturerFragment: CapturerFragment
+
+ private var cameraPreviewListenerList: ArrayList? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(com.suaro.pidroidapp.R.layout.activity_image)
+
+ capturerLayout = findViewById(R.id.capturer_fragment)
+ imageTest = findViewById(R.id.photo)
+ setupNative()
+ }
+
+ private fun setupNative() {
+ val pidroidConfig = PidroidConfig()
+ Pidroid.setup(this, pidroidConfig)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ val handler: Handler = Handler();
+ onCameraStart()
+ handler.postDelayed({
+ val drawable: BitmapDrawable = imageTest.getDrawable() as BitmapDrawable
+ val bitmap: Bitmap = drawable.bitmap
+ onPreviewFrame(bitmap)
+ }, 1000)
+
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ }
+
+ override fun onCameraStart() {
+ loadCapturerFragment()
+ }
+
+ override fun onPreviewFrame(bitmap: Bitmap) {
+ cameraPreviewListenerList!!.forEach {
+ it.onPreviewFrame(bitmap)
+ }
+ }
+
+ override fun onCapturerFinish() {
+ Log.i("Capturer", "Finish")
+ }
+
+
+ fun loadCapturerFragment() {
+ capturerFragment = CapturerFragment.newInstance();
+ val transaction: FragmentTransaction = supportFragmentManager.beginTransaction()
+ transaction.replace(R.id.capturer_fragment, capturerFragment)
+ transaction.commit()
+ }
+
+ fun addCameraPreviewListener(listener: CameraPreviewListener) {
+ if (cameraPreviewListenerList == null) {
+ cameraPreviewListenerList = ArrayList()
+ }
+ cameraPreviewListenerList!!.add(listener)
+ }
+}
diff --git a/app/src/main/java/com/suaro/pidroidapp/MainActivity.kt b/app/src/main/java/com/suaro/pidroidapp/MainActivity.kt
new file mode 100644
index 0000000..ccec0ae
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/MainActivity.kt
@@ -0,0 +1,92 @@
+package com.suaro.pidroidapp
+
+import android.content.res.AssetManager
+import android.graphics.Bitmap
+import android.os.Bundle
+import android.util.Log
+import android.widget.FrameLayout
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.FragmentTransaction
+import com.suaro.pidroid.Pidroid
+import com.suaro.pidroid.core.PidroidConfig
+import com.suaro.pidroidapp.camera.Camera
+import com.suaro.pidroidapp.camera.view.CameraFragment
+import com.suaro.pidroidapp.capturer.Capturer
+import com.suaro.pidroidapp.capturer.view.CapturerFragment
+import com.suaro.pidroidapp.core.CameraPreviewListener
+import java.util.*
+
+
+class MainActivity : AppCompatActivity(), Camera.View, Capturer.View{
+
+ private lateinit var capturerLayout: FrameLayout
+ private lateinit var cameraLayout: FrameLayout
+
+ private lateinit var capturerFragment: CapturerFragment
+ private lateinit var cameraFragment: CameraFragment
+
+ private var cameraPreviewListenerList: ArrayList? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ cameraLayout = findViewById(R.id.camera_fragment)
+ capturerLayout = findViewById(R.id.capturer_fragment)
+
+ setupNative()
+ loadCameraFragment()
+ }
+
+ private fun setupNative() {
+ val pidroidConfig = PidroidConfig()
+ Pidroid.setup(this, pidroidConfig)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ }
+
+ override fun onCameraStart() {
+ loadCapturerFragment()
+ }
+
+ override fun onPreviewFrame(bitmap: Bitmap) {
+ cameraPreviewListenerList!!.forEach {
+ it.onPreviewFrame(bitmap)
+ }
+ }
+
+ override fun onCapturerFinish() {
+ Log.i("Capturer", "Finish")
+ }
+
+ fun loadCameraFragment() {
+ cameraFragment = CameraFragment.newInstance();
+ val transaction: FragmentTransaction = supportFragmentManager.beginTransaction()
+ transaction.replace(R.id.camera_fragment, cameraFragment)
+ transaction.commit()
+ }
+
+ fun loadCapturerFragment() {
+ capturerFragment = CapturerFragment.newInstance();
+ val transaction: FragmentTransaction = supportFragmentManager.beginTransaction()
+ transaction.replace(R.id.capturer_fragment, capturerFragment)
+ transaction.commit()
+ }
+
+ fun addCameraPreviewListener(listener: CameraPreviewListener) {
+ if (cameraPreviewListenerList == null) {
+ cameraPreviewListenerList = ArrayList()
+ }
+ cameraPreviewListenerList!!.add(listener)
+ }
+}
diff --git a/app/src/main/java/com/suaro/pidroidapp/camera/Camera.kt b/app/src/main/java/com/suaro/pidroidapp/camera/Camera.kt
new file mode 100644
index 0000000..b887c1d
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/camera/Camera.kt
@@ -0,0 +1,14 @@
+package com.suaro.pidroidapp.camera
+
+import android.graphics.Bitmap
+
+interface Camera {
+ interface View{
+ fun onCameraStart()
+ fun onPreviewFrame(bitmap: Bitmap)
+ }
+
+ interface Presenter {
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/camera/entity/Flash.kt b/app/src/main/java/com/suaro/pidroidapp/camera/entity/Flash.kt
new file mode 100644
index 0000000..59d8f2b
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/camera/entity/Flash.kt
@@ -0,0 +1,5 @@
+package com.suaro.pidroidapp.camera.entity
+
+enum class Flash {
+ ON, OFF
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/camera/view/AutoFitTextureView.kt b/app/src/main/java/com/suaro/pidroidapp/camera/view/AutoFitTextureView.kt
new file mode 100644
index 0000000..a0ccbfe
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/camera/view/AutoFitTextureView.kt
@@ -0,0 +1,49 @@
+package com.suaro.pidroidapp.camera.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.TextureView
+
+class AutoFitTextureView : TextureView {
+
+ private var mRatioWidth = 0
+ private var mRatioHeight = 0
+
+ constructor(context: Context) : this(context, null)
+
+ constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)
+
+ constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle)
+
+ /**
+ * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
+ * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
+ * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
+ *
+ * @param width Relative horizontal size
+ * @param height Relative vertical size
+ */
+ fun setAspectRatio(width: Int, height: Int) {
+ if (width < 0 || height < 0) {
+ throw IllegalArgumentException("Size cannot be negative.")
+ }
+ mRatioWidth = width
+ mRatioHeight = height
+ requestLayout()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ val width = MeasureSpec.getSize(widthMeasureSpec)
+ val height = MeasureSpec.getSize(heightMeasureSpec)
+ if (0 == mRatioWidth || 0 == mRatioHeight) {
+ setMeasuredDimension(width, height)
+ } else {
+ if (width < height * mRatioWidth / mRatioHeight) {
+ setMeasuredDimension(width, width * mRatioHeight / mRatioWidth)
+ } else {
+ setMeasuredDimension(height * mRatioWidth / mRatioHeight, height)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/camera/view/CameraFragment.kt b/app/src/main/java/com/suaro/pidroidapp/camera/view/CameraFragment.kt
new file mode 100644
index 0000000..46feacf
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/camera/view/CameraFragment.kt
@@ -0,0 +1,127 @@
+package com.suaro.pidroidapp.camera.view
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import com.suaro.pidroidapp.R
+import com.suaro.pidroidapp.camera.Camera
+
+class CameraFragment : Fragment() {
+
+ private val CAMERA_PERMISSION: Int = 1001
+ private lateinit var cameraView: Camera.View
+ private lateinit var camera2: CameraManager
+ private lateinit var v: View
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ cameraView = if (context is Camera.View) {
+ context
+ } else {
+ throw ClassCastException(
+ context.toString() + " must implement Camera.View"
+ )
+ }
+ }
+
+ override fun onCreate(savedInstance: Bundle?) {
+ super.onCreate(savedInstance)
+
+ }
+
+ override fun onCreateView(
+ layoutInflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ v = layoutInflater.inflate(R.layout.camera_fragment, container, false)
+ ?: return null
+ setRetainInstance(true)
+
+ if (ContextCompat.checkSelfPermission(
+ activity!!,
+ Manifest.permission.CAMERA
+ ) == PackageManager.PERMISSION_GRANTED
+ )
+
+ camera2 = CameraManager(activity!!, v.findViewById(R.id.camera_view), cameraView)
+ else {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ ActivityCompat.requestPermissions(activity!!, arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE), CAMERA_PERMISSION)
+ }
+ else {
+ camera2 = CameraManager(activity!!, v.findViewById(R.id.camera_view), cameraView)
+ }
+
+ }
+
+ return v
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (::camera2.isInitialized) {
+ camera2.start()
+ }
+
+ }
+
+ override fun onPause() {
+ super.onPause()
+ if (::camera2.isInitialized) {
+ camera2.stop()
+ }
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ when (requestCode) {
+ CAMERA_PERMISSION -> {
+ // If request is cancelled, the result arrays are empty.
+ if ((grantResults.isNotEmpty() &&
+ grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
+ camera2 = CameraManager(activity!!, v.findViewById(R.id.camera_view), cameraView)
+ if (::camera2.isInitialized) {
+ camera2.start()
+ }
+ } else {
+ Toast.makeText(this.context, "You must accept camera permissions to continue", Toast.LENGTH_LONG).show()
+ }
+ return
+ }
+
+ else -> {
+ // Ignore all other requests.
+ }
+ }
+ }
+
+ companion object {
+ private val TAG = CameraFragment::class.java.simpleName
+ fun newInstance(): CameraFragment {
+ val f = CameraFragment()
+ //val args = Bundle()
+ //args.putParcelable("instructions_item", item)
+ //f.setArguments(args)
+ return f
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/camera/view/CameraManager.kt b/app/src/main/java/com/suaro/pidroidapp/camera/view/CameraManager.kt
new file mode 100644
index 0000000..8b72527
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/camera/view/CameraManager.kt
@@ -0,0 +1,493 @@
+package com.suaro.pidroidapp.camera.view
+
+import android.Manifest
+import android.app.Activity
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.res.Configuration
+import android.graphics.*
+import android.graphics.Matrix.ScaleToFit
+import android.graphics.Point
+import android.hardware.camera2.*
+import android.hardware.camera2.CameraManager
+import android.os.Handler
+import android.os.HandlerThread
+import android.util.Log
+import android.util.Size
+import android.util.SparseIntArray
+import android.view.Surface
+import android.view.TextureView
+import androidx.core.content.ContextCompat
+import com.suaro.pidroidapp.camera.entity.Flash
+import java.util.*
+import java.util.Collections.singletonList
+import kotlin.Comparator
+
+
+class CameraManager(private val activity: Activity, private val textureView: AutoFitTextureView, private val cameraView: com.suaro.pidroidapp.camera.Camera.View) {
+
+ private var first: Boolean = true
+
+ private val cameraManager: CameraManager =
+ textureView.context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ private var cameraFacing = CameraCharacteristics.LENS_FACING_FRONT
+ private var previewSize: Size? = null
+ //Current Camera id
+ private var cameraId = "-1"
+ private var backgroundHandler: Handler? = null
+ private var backgroundThread: HandlerThread? = null
+ private var cameraDevice: CameraDevice? = null
+ //
+ private var cameraCaptureSession: CameraCaptureSession? = null
+ // capture request builder for camera.
+ private var captureRequestBuilder: CaptureRequest.Builder? = null
+ // capture request generated by above builder.
+ private var captureRequest: CaptureRequest? = null
+ private var flash = Flash.OFF
+
+ private var surface: Surface? = null
+ private var isFlashSupported = true
+ private var mSensorOrientation = 0
+
+
+ private companion object {
+
+ private val ORIENTATIONS = SparseIntArray()
+
+ init {
+ ORIENTATIONS.append(Surface.ROTATION_0, 90)
+ ORIENTATIONS.append(Surface.ROTATION_90, 0)
+ ORIENTATIONS.append(Surface.ROTATION_180, 270)
+ ORIENTATIONS.append(Surface.ROTATION_270, 180)
+ }
+
+
+ private const val MAX_PREVIEW_WIDTH = 640
+ private const val MAX_PREVIEW_HEIGHT = 640
+
+
+ /**
+ * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that
+ * is at least as large as the respective texture view size, and that is at most as large as the
+ * respective max size, and whose aspect ratio matches with the specified value. If such size
+ * doesn't exist, choose the largest one that is at most as large as the respective max size,
+ * and whose aspect ratio matches with the specified value.
+ *
+ * @param choices The list of sizes that the camera supports for the intended output
+ * class
+ * @param textureViewWidth The width of the texture view relative to sensor coordinate
+ * @param textureViewHeight The height of the texture view relative to sensor coordinate
+ * @param maxWidth The maximum width that can be chosen
+ * @param maxHeight The maximum height that can be chosen
+ * @param aspectRatio The aspect ratio
+ * @return The optimal {@code Size}, or an arbitrary one if none were big enough
+ */
+ private fun chooseOptimalSize(
+ choices: Array, textureViewWidth: Int,
+ textureViewHeight: Int, maxWidth: Int, maxHeight: Int, aspectRatio: Size
+ ): Size {
+
+ // Collect the supported resolutions that are at least as big as the preview Surface
+ val bigEnough = arrayListOf()
+ // Collect the supported resolutions that are smaller than the preview Surface
+ val notBigEnough = arrayListOf()
+ val w = aspectRatio.width
+ val h = aspectRatio.height
+ for (option in choices) {
+ if (option.width <= maxWidth && option.height <= maxHeight
+ ) {
+ if (option.width >= textureViewWidth && option.height >= textureViewHeight) {
+ bigEnough.add(option)
+ } else {
+ notBigEnough.add(option)
+ }
+ }
+ }
+
+ // Pick the smallest of those big enough. If there is no one big enough, pick the
+ // largest of those not big enough.
+
+ return when {
+ bigEnough.isNotEmpty() -> Collections.min(bigEnough, compareSizesByArea)
+ notBigEnough.isNotEmpty() -> Collections.max(notBigEnough, compareSizesByArea)
+ else -> {
+ Log.e("Camera", "Couldn't find any suitable preview size")
+ choices[0]
+ }
+ }
+ }
+
+ private val compareSizesByArea = Comparator { lhs, rhs ->
+ // We cast here to ensure the multiplications won't overflow
+ java.lang.Long.signum(lhs.width.toLong() * lhs.height - rhs.width.toLong() * rhs.height)
+ }
+
+ }
+
+ private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
+ override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
+ configureTransform(width, height)
+ }
+
+ override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
+
+ if(first) {
+ //First frame is deleted because is empty.
+ first = false
+ return;
+ }
+
+ val frame = Bitmap.createBitmap(
+ textureView.width,
+ textureView.height,
+ Bitmap.Config.ARGB_8888
+ )
+ textureView.getBitmap(frame)
+
+ cameraView.onPreviewFrame(frame)
+ }
+
+ override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
+ return true
+ }
+
+ override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
+ openCamera(width, height)
+
+ }
+ }
+
+ private val cameraStateCallback = object : CameraDevice.StateCallback() {
+ override fun onOpened(camera: CameraDevice) {
+ this@CameraManager.cameraDevice = camera
+ createPreviewSession()
+ }
+
+ override fun onDisconnected(camera: CameraDevice) {
+ camera.close()
+ this@CameraManager.cameraDevice = null
+ }
+
+ override fun onError(camera: CameraDevice, error: Int) {
+ }
+ }
+
+ fun start() {
+ openBackgroundThread()
+ if (textureView.isAvailable) {
+ openCamera(textureView.width, textureView.height)
+ } else {
+ textureView.surfaceTextureListener = surfaceTextureListener
+ }
+
+ }
+
+ fun stop() {
+ closeCamera()
+ closeBackgroundThread()
+
+ }
+
+
+ private fun closeCamera() {
+ if (cameraCaptureSession != null) {
+ cameraCaptureSession!!.close()
+ cameraCaptureSession = null
+ // cameraSessionClosed = true
+ }
+
+ if (cameraDevice != null) {
+ cameraDevice!!.close()
+ cameraDevice = null
+ }
+ }
+
+ private fun closeBackgroundThread() {
+ if (backgroundHandler != null) {
+ backgroundThread!!.quitSafely()
+ backgroundThread = null
+ backgroundHandler = null
+ }
+ }
+
+
+ private fun openCamera(width: Int, height: Int) {
+ if (ContextCompat.checkSelfPermission(
+ textureView.context,
+ Manifest.permission.CAMERA
+ ) == PackageManager.PERMISSION_GRANTED
+ ) {
+ setUpCameraOutputs(width, height)
+ configureTransform(width, height)
+
+ cameraManager.openCamera(cameraId, cameraStateCallback, backgroundHandler)
+ cameraView.onCameraStart()
+
+ } else Log.e("Camera2", "Requires Camera Permission")
+ }
+
+ /**
+ * Sets up member variables related to camera.
+ *
+ * @param width The width of available size for camera preview
+ * @param height The height of available size for camera preview
+ */
+ private fun setUpCameraOutputs(width: Int, height: Int) {
+ try {
+ for (cameraId in cameraManager.cameraIdList) {
+ val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
+ val cameraFacing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)
+
+ if (cameraFacing == this.cameraFacing) {
+ val streamConfigurationMap = cameraCharacteristics.get(
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
+ )
+// For still image captures, we use the largest available size.
+ val largest = Collections.max(
+ streamConfigurationMap?.getOutputSizes(ImageFormat.JPEG)?.toList(),
+ compareSizesByArea
+ )
+
+// Find out if we need to swap dimension to get the preview size relative to sensor
+// coordinate.
+ val displayRotation = activity.windowManager.defaultDisplay.rotation
+
+ //noinspection ConstantConditions
+ mSensorOrientation = cameraCharacteristics[CameraCharacteristics.SENSOR_ORIENTATION] ?: 0
+
+ var swappedDimensions = false
+
+ when (displayRotation) {
+ Surface.ROTATION_0 -> {
+ }
+ Surface.ROTATION_90 -> {
+ }
+ Surface.ROTATION_180 -> {
+ swappedDimensions =
+ mSensorOrientation == 90 || mSensorOrientation == 270
+ }
+ Surface.ROTATION_270 -> {
+ swappedDimensions = mSensorOrientation == 0 || mSensorOrientation == 180
+ }
+ else -> Log.e("Camera2", "Display rotation is invalid: $displayRotation")
+
+ }
+
+ val displaySize = Point()
+
+ activity.windowManager.defaultDisplay.getSize(displaySize)
+
+ var rotatedPreviewWidth = width
+ var rotatedPreviewHeight = height
+ var maxPreviewWidth = displaySize.x
+ var maxPreviewHeight = displaySize.y
+
+ if (swappedDimensions) {
+ rotatedPreviewWidth = height
+ rotatedPreviewHeight = width
+ maxPreviewWidth = displaySize.y
+ maxPreviewHeight = displaySize.x
+ }
+
+ if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
+ maxPreviewWidth = MAX_PREVIEW_WIDTH
+ }
+
+ if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
+ maxPreviewHeight = MAX_PREVIEW_HEIGHT
+ }
+
+
+ // Danger, W.R.! Attempting to use too large a preview size could exceed the camera
+ // bus' bandwidth limitation, resulting in gorgeous previews but the storage of
+ // garbage capture data.
+ previewSize = chooseOptimalSize(
+ streamConfigurationMap!!.getOutputSizes(SurfaceTexture::class.java),
+ rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
+ maxPreviewHeight, largest
+ )
+
+ // We fit the aspect ratio of TextureView to the size of preview we picked.
+ val orientation = activity.resources.configuration.orientation
+
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ textureView.setAspectRatio(
+ previewSize!!.width, previewSize!!.height
+ )
+ } else {
+ textureView.setAspectRatio(
+ previewSize!!.height, previewSize!!.width
+ )
+ }
+ // check flash support
+ val flashSupported = cameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)
+ isFlashSupported = flashSupported == null ?: false
+
+ this.cameraId = cameraId
+
+ return
+ }
+ }
+ } catch (e: CameraAccessException) {
+ e.printStackTrace()
+ }
+ }
+
+ /**
+ * Configures the necessary [android.graphics.Matrix] transformation to `mTextureView`.
+ * This method should be called after the camera preview size is determined in
+ * setUpCameraOutputs and also the size of `mTextureView` is fixed.
+ *
+ * @param viewWidth The width of `mTextureView`
+ * @param viewHeight The height of `mTextureView`
+ */
+ private fun configureTransform(viewWidth: Int, viewHeight: Int) {
+ val rotation = activity.windowManager.defaultDisplay.rotation
+ val matrix = Matrix()
+ val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat())
+ val bufferRect = RectF(
+ 0f,
+ 0f,
+ previewSize!!.height.toFloat(),
+ previewSize!!.width.toFloat()
+ )
+ val centerX = viewRect.centerX()
+ val centerY = viewRect.centerY()
+ if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
+ bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY())
+ matrix.setRectToRect(viewRect, bufferRect, ScaleToFit.FILL)
+ val scale = Math.max(
+ viewHeight.toFloat() / previewSize!!.height,
+ viewWidth.toFloat() / previewSize!!.width
+ )
+ matrix.postScale(scale, scale, centerX, centerY)
+ matrix.postRotate((90 * (rotation - 2)).toFloat(), centerX, centerY)
+ } else if (Surface.ROTATION_180 == rotation) {
+ matrix.postRotate(180f, centerX, centerY)
+ }
+ textureView.setTransform(matrix)
+ }
+
+ /**
+ * Retrieves the JPEG orientation from the specified screen rotation.
+ *
+ * @param rotation The screen rotation.
+ * @return The JPEG orientation (one of 0, 90, 270, and 360)
+ */
+ private fun getOrientation(rotation: Int) =
+
+ // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
+ // We have to take that into account and rotate JPEG properly.
+ // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS.
+ // For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
+ (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360
+
+
+ private fun openBackgroundThread() {
+ backgroundThread = HandlerThread("camera_background_thread")
+ backgroundThread!!.start()
+ backgroundHandler = Handler(backgroundThread!!.looper)
+ }
+
+ // Creates a new camera preview session
+ private fun createPreviewSession() {
+
+ try {
+
+ val surfaceTexture = textureView.surfaceTexture
+// We configure the size of default buffer to be the size of camera preview we want.
+ surfaceTexture?.setDefaultBufferSize(previewSize!!.width, previewSize!!.height)
+
+// This is the output Surface we need to start preview.
+ if (surface == null)
+ surface = Surface(surfaceTexture)
+
+ val previewSurface = surface
+
+ //val mImageSurface: Surface = imageReader!!.getSurface()
+
+ captureRequestBuilder = cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
+ //captureRequestBuilder!!.addTarget(mImageSurface)
+ captureRequestBuilder!!.addTarget(previewSurface!!)
+
+// Here, we create a CameraCaptureSession for camera preview.
+
+ cameraDevice!!.createCaptureSession(
+ singletonList(previewSurface),
+ object : CameraCaptureSession.StateCallback() {
+
+ override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
+ if (cameraDevice == null) {
+ return
+ }
+
+ try {
+// When session is ready we start displaying preview.
+ this@CameraManager.cameraCaptureSession = cameraCaptureSession
+ // cameraSessionClosed = false
+
+ this@CameraManager.configureCapture()
+
+ } catch (e: CameraAccessException) {
+ e.printStackTrace()
+ }
+
+ }
+
+ override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) {
+
+ }
+ }, backgroundHandler
+ )
+ } catch (e: CameraAccessException) {
+ e.printStackTrace()
+ }
+
+ }
+
+ private fun configureCapture() {
+ if (flash == Flash.ON) {
+ captureRequestBuilder!!.set(
+ CaptureRequest.FLASH_MODE,
+ CaptureRequest.FLASH_MODE_TORCH
+ )
+ } else {
+ captureRequestBuilder!!.set(
+ CaptureRequest.FLASH_MODE,
+ CaptureRequest.FLASH_MODE_OFF
+ )
+ }
+
+ captureRequestBuilder!!.set(
+ CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON
+ )
+
+ captureRequestBuilder!!.set(
+ CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+ )
+
+// Finally, we start displaying the camera preview.
+ captureRequest = captureRequestBuilder!!.build()
+
+ this@CameraManager.cameraCaptureSession!!.setRepeatingRequest(
+ captureRequest!!,
+ null,
+ backgroundHandler
+ )
+ }
+
+ fun setFlash(flash: Flash) {
+
+ this.flash = flash
+
+ if (textureView.context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) && !cameraId.equals(
+ "-1"
+ )) {
+ configureCapture()
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/capturer/Capturer.kt b/app/src/main/java/com/suaro/pidroidapp/capturer/Capturer.kt
new file mode 100644
index 0000000..224ab67
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/capturer/Capturer.kt
@@ -0,0 +1,11 @@
+package com.suaro.pidroidapp.capturer
+
+interface Capturer {
+ interface View{
+ fun onCapturerFinish()
+ }
+
+ interface Presenter {
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/capturer/ImageProcessor.kt b/app/src/main/java/com/suaro/pidroidapp/capturer/ImageProcessor.kt
new file mode 100644
index 0000000..444ea08
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/capturer/ImageProcessor.kt
@@ -0,0 +1,26 @@
+package com.suaro.pidroidapp.capturer
+
+import android.graphics.Bitmap
+import com.suaro.pidroid.core.FaceDetectionResult
+
+
+interface ImageProcessor {
+ interface Interactor {
+ fun detectFace(intArray: IntArray, width: Int, height: Int)
+ fun detectFace(bitmap: Bitmap)
+
+ interface FinishListener {
+ fun onFaceDetectorFinish(info: FaceDetectionResult)
+ }
+ }
+
+ interface View {
+ fun onFaceDetectorFinish(info: FaceDetectionResult)
+ }
+
+ interface Presenter {
+ fun detectFace(intArray: IntArray, width: Int, height: Int)
+ fun detectFace(bitmap: Bitmap)
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/capturer/entity/Circle.kt b/app/src/main/java/com/suaro/pidroidapp/capturer/entity/Circle.kt
new file mode 100644
index 0000000..ef87789
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/capturer/entity/Circle.kt
@@ -0,0 +1,15 @@
+package com.suaro.pidroidapp.capturer.entity
+
+import com.suaro.pidroid.core.Point
+
+
+class Circle {
+
+ constructor(center: Point, radius: Int) {
+ this.center = center
+ this.radius = radius
+ }
+
+ var center: Point = Point(0,0)
+ var radius: Int = 0
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/capturer/interactor/BaseFrameProcessor.kt b/app/src/main/java/com/suaro/pidroidapp/capturer/interactor/BaseFrameProcessor.kt
new file mode 100644
index 0000000..a1038b5
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/capturer/interactor/BaseFrameProcessor.kt
@@ -0,0 +1,112 @@
+package com.suaro.pidroidapp.capturer.interactor
+
+import android.graphics.Bitmap
+import android.os.SystemClock
+import com.suaro.pidroidapp.capturer.ImageProcessor
+import com.suaro.pidroidapp.core.Utils
+import java.nio.ByteBuffer
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+
+abstract class BaseFrameProcessor(protected var listener: ImageProcessor.Interactor.FinishListener) :
+ Runnable {
+
+ private val mStartTimeMillis = SystemClock.elapsedRealtime()
+
+ // This lock guards all of the member variables below.
+ protected val mLock = ReentrantLock()
+ protected val condition = mLock.newCondition()
+
+ protected var mActive = true
+ private val processingThread: Thread? = null
+
+ // These pending variables hold the state associated with the new frame awaiting processing.
+ private var mPendingTimeMillis: Long = 0
+ private var mPendingFrameId = 0
+ protected var mPendingFrameData: IntArray? = null
+
+ protected var previewWidth = 0
+ protected var previewHeight = 0
+ private var proccessingThread: Thread? = null
+
+
+ fun setProcessingThread(proccessingThread: Thread?) {
+ mLock.withLock {
+ this.proccessingThread = proccessingThread
+ condition.signalAll()
+ }
+ }
+
+ /**
+ * Marks the runnable as active/not active. Signals any blocked threads to continue.
+ */
+ fun setActive(active: Boolean) {
+ mLock.withLock {
+ mActive = active
+ condition.signalAll()
+ }
+ }
+
+ /**
+ * Sets the frame data received from the camera. This adds the previous unused frame buffer
+ * (if present) back to the camera, and keeps a pending reference to the frame data for
+ * future use.
+ */
+ fun setNextFrame(
+ data: IntArray?,
+ previewWidth: Int,
+ previewHeight: Int
+ ) {
+ mLock.withLock {
+ this.previewHeight = previewHeight
+ this.previewWidth = previewWidth
+
+ if (mPendingFrameData != null) {
+ mPendingFrameData = null
+ }
+
+ // Timestamp and frame ID are maintained here, which will give downstream code some
+ // idea of the timing of frames received and when frames were dropped along the way.
+ mPendingTimeMillis = SystemClock.elapsedRealtime() - mStartTimeMillis
+ mPendingFrameId++
+ mPendingFrameData = data
+
+ // Notify the processor thread if it is waiting on the next frame (see below).
+ condition.signalAll()
+ }
+ }
+
+ /**
+ * Sets the frame data received from the camera. This adds the previous unused frame buffer
+ * (if present) back to the camera, and keeps a pending reference to the frame data for
+ * future use.
+ */
+ fun setNextFrame(data: Bitmap?
+ ) {
+ mLock.withLock {
+ this.previewHeight = data!!.height
+ this.previewWidth = data.width
+
+ if (mPendingFrameData != null) {
+ mPendingFrameData = null
+ }
+
+ // Timestamp and frame ID are maintained here, which will give downstream code some
+ // idea of the timing of frames received and when frames were dropped along the way.
+ mPendingTimeMillis = SystemClock.elapsedRealtime() - mStartTimeMillis
+ mPendingFrameId++
+ mPendingFrameData = Utils.getBytes(data)!!
+
+ // Notify the processor thread if it is waiting on the next frame (see below).
+ condition.signalAll()
+ }
+ }
+
+ override fun run() {
+
+ }
+
+ init {
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/capturer/interactor/FaceDetectorFrameProcessor.kt b/app/src/main/java/com/suaro/pidroidapp/capturer/interactor/FaceDetectorFrameProcessor.kt
new file mode 100644
index 0000000..d304e0a
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/capturer/interactor/FaceDetectorFrameProcessor.kt
@@ -0,0 +1,51 @@
+package com.suaro.pidroidapp.capturer.interactor
+
+import android.util.Log
+import com.suaro.pidroid.Pidroid
+import com.suaro.pidroid.core.FaceDetectionResult
+import com.suaro.pidroidapp.capturer.ImageProcessor
+import kotlin.concurrent.withLock
+
+class FaceDetectorFrameProcessor(listener: ImageProcessor.Interactor.FinishListener) :
+ BaseFrameProcessor(listener) {
+
+ companion object {
+ private val TAG = FaceDetectorFrameProcessor::class.java.simpleName
+ }
+
+ fun detect(byteArray: IntArray, width: Int, height: Int): FaceDetectionResult {
+ var dInfo = FaceDetectionResult()
+ Pidroid.detectFace(byteArray, width, height, dInfo)
+ return dInfo
+ }
+
+ override fun run() {
+ super.run()
+ var outputFrame: IntArray
+
+ while (true) {
+ mLock.withLock {
+ while (mActive && mPendingFrameData == null) {
+ try {
+ condition.await()
+ } catch (e: InterruptedException) {
+ return
+ }
+ }
+ if (!mActive) {
+ return
+ }
+ outputFrame = mPendingFrameData!!
+ mPendingFrameData = null
+ }
+ try {
+ val detectionInfo: FaceDetectionResult = this.detect(outputFrame, previewWidth, previewHeight)
+ listener.onFaceDetectorFinish(detectionInfo)
+ } catch (t: Throwable) {
+ Log.e(FaceDetectorFrameProcessor.TAG, t.message.toString())
+ }
+ }
+
+ }
+}
+
diff --git a/app/src/main/java/com/suaro/pidroidapp/capturer/interactor/ImageProcessorInteractor.kt b/app/src/main/java/com/suaro/pidroidapp/capturer/interactor/ImageProcessorInteractor.kt
new file mode 100644
index 0000000..4d75c2a
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/capturer/interactor/ImageProcessorInteractor.kt
@@ -0,0 +1,30 @@
+package com.suaro.pidroidapp.capturer.interactor
+
+import android.content.Context
+import android.graphics.Bitmap
+import com.suaro.pidroidapp.capturer.ImageProcessor
+
+class ImageProcessorInteractor(private var context: Context, private var listener: ImageProcessor.Interactor.FinishListener) : ImageProcessor.Interactor {
+
+ private var faceDetectorFrameProcessor: FaceDetectorFrameProcessor
+
+ private var faceDetectorProcessingThread: Thread
+
+ init {
+ faceDetectorFrameProcessor = FaceDetectorFrameProcessor(listener)
+ faceDetectorProcessingThread = Thread(faceDetectorFrameProcessor)
+
+ faceDetectorFrameProcessor.setProcessingThread(faceDetectorProcessingThread)
+ faceDetectorFrameProcessor.setActive(true)
+
+ faceDetectorProcessingThread.start()
+ }
+
+ override fun detectFace(intArray: IntArray, width: Int, height: Int) {
+ faceDetectorFrameProcessor.setNextFrame(intArray, width, height)
+ }
+
+ override fun detectFace(bitmap: Bitmap) {
+ faceDetectorFrameProcessor.setNextFrame(bitmap)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/capturer/presenter/ImageProcessorPresenter.kt b/app/src/main/java/com/suaro/pidroidapp/capturer/presenter/ImageProcessorPresenter.kt
new file mode 100644
index 0000000..1e24de3
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/capturer/presenter/ImageProcessorPresenter.kt
@@ -0,0 +1,28 @@
+package com.suaro.pidroidapp.capturer.presenter
+
+import android.content.Context
+import android.graphics.Bitmap
+import com.suaro.pidroid.core.FaceDetectionResult
+import com.suaro.pidroidapp.capturer.ImageProcessor
+import com.suaro.pidroidapp.capturer.interactor.ImageProcessorInteractor
+
+class ImageProcessorPresenter(private var context: Context, private val view: ImageProcessor.View): ImageProcessor.Presenter, ImageProcessor.Interactor.FinishListener {
+
+ private val interactor: ImageProcessorInteractor
+
+ init {
+ interactor = ImageProcessorInteractor(context, this)
+ }
+
+ override fun detectFace(intArray: IntArray, width: Int, height: Int) {
+ interactor.detectFace(intArray, width, height)
+ }
+
+ override fun detectFace(bitmap: Bitmap) {
+ interactor.detectFace(bitmap)
+ }
+
+ override fun onFaceDetectorFinish(info: FaceDetectionResult) {
+ view.onFaceDetectorFinish(info)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/capturer/view/CanvasView.kt b/app/src/main/java/com/suaro/pidroidapp/capturer/view/CanvasView.kt
new file mode 100644
index 0000000..50663bd
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/capturer/view/CanvasView.kt
@@ -0,0 +1,153 @@
+package com.suaro.pidroidapp.capturer.view
+
+import android.content.Context
+import android.graphics.*
+import android.util.AttributeSet
+import android.view.View
+import com.suaro.pidroidapp.capturer.entity.Circle
+import com.suaro.pidroid.core.Point
+
+class CanvasView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0): View(context, attrs, defStyleAttr) {
+
+ private val RECT_BORDER_RADIUS = 25f;
+ private val RECT_BORDER_WIDTH = 10f;
+
+ var faces: ArrayList = ArrayList();
+ var eyes: ArrayList = ArrayList();
+ var landmarks: ArrayList = ArrayList();
+
+ var paint: Paint = Paint()
+
+ private var mRatioWidth: Double = 0.0
+ private var mRatioHeight: Double = 0.0
+
+ private var lastRatioWidth: Double = 0.0
+ private var lastRatioHeight: Double = 0.0
+
+ private var currentRatio: Double = 0.0
+ private var currentWidth: Double = 0.0
+ private var currentHeight: Double = 0.0
+
+ private var widthScaleFactor: Double = 1.0;
+ private var heightScaleFactor: Double = 1.0;
+
+ fun drawRect(rect: Rect, canvas: Canvas?, color: Int = Color.GREEN) {;
+
+ paint.setStyle(Paint.Style.STROKE)
+ paint.setColor(color)
+ paint.setStrokeWidth(RECT_BORDER_WIDTH);
+
+ canvas?.drawRoundRect(rect.left.toFloat(), rect.top.toFloat(), rect.right.toFloat(), rect.bottom.toFloat(), RECT_BORDER_RADIUS, RECT_BORDER_RADIUS, paint);
+ }
+
+ fun drawCircle(circle: Circle, canvas: Canvas?, color: Int = Color.GREEN) {;
+
+ paint.setStyle(Paint.Style.FILL)
+ paint.setColor(color)
+
+ canvas?.drawCircle(
+ circle.center.x.toFloat(),
+ circle.center.y.toFloat(), circle.radius.toFloat(), paint)
+ }
+
+ // Called when the view should render its content.
+ override fun onDraw(canvas: Canvas?) {
+ super.onDraw(canvas)
+
+ faces.forEach {
+ drawRect(getScaleRect(it), canvas, Color.GREEN);
+ }
+
+ eyes.forEach {
+ drawCircle(scaleCircle(it), canvas, Color.RED);
+ }
+
+ landmarks.forEach {
+ drawCircle(scaleCircle(it), canvas, Color.BLUE);
+ }
+ }
+
+ private fun scaleCircle(it: Circle): Circle {
+ val x = translateX(it.center.x.toInt())
+ val y = translateY(it.center.y.toInt())
+ val radius = scaleY(it.radius)
+
+ return Circle(Point(x.toInt(),y.toInt()), radius.toInt())
+ }
+
+ private fun getScaleRect(rect: Rect): Rect {
+ val xRect = rect.left;
+ val yRect = rect.top;
+ val x: Float = translateX(xRect + rect.width() / 2)
+ val y: Float = translateY(yRect + rect.height() / 2)
+
+ // Draws a bounding box around the face.
+ val xOffset: Float = scaleX((rect.width() / 2.0f).toInt())
+ val left = x - xOffset
+ val right = x + xOffset
+ val width = right - left
+ val height = width
+ val yOffset = height / 2
+ val top = y - yOffset
+ val bottom = y + yOffset
+ return Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
+ }
+
+ private fun translateX(x: Int): Float {
+ return scaleX(x)
+ }
+
+ private fun scaleX(x: Int): Float {
+ return (x*widthScaleFactor).toFloat();
+ }
+
+ private fun translateY(y: Int): Float {
+ return scaleY(y)
+ }
+
+ private fun scaleY(y: Int): Float {
+ return (y*heightScaleFactor).toFloat();
+ }
+
+ fun redraw() {
+ this.invalidate()
+ }
+
+ fun setAspectRatio(width: Int, height: Int) {
+ if (width < 0 || height < 0) {
+ throw IllegalArgumentException("Size cannot be negative.")
+ }
+
+ mRatioWidth = width.toDouble()
+ mRatioHeight = height.toDouble()
+
+ if (this.measuredWidth < this.measuredHeight * mRatioWidth / mRatioHeight) {
+ currentWidth = this.measuredWidth.toDouble()
+ currentRatio = mRatioHeight / mRatioWidth
+ currentHeight = this.measuredWidth * currentRatio
+ } else {
+ currentHeight = this.measuredHeight.toDouble()
+ currentRatio = mRatioWidth / mRatioHeight
+ currentWidth = this.measuredHeight * currentRatio
+ }
+
+
+ requestLayout()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ val width = MeasureSpec.getSize(widthMeasureSpec)
+ val height = MeasureSpec.getSize(heightMeasureSpec)
+ if (0.0 == this.currentWidth || 0.0 == this.currentHeight) {
+ currentWidth = width.toDouble()
+ currentHeight = height.toDouble()
+ }
+
+ widthScaleFactor = currentWidth / mRatioWidth;
+ heightScaleFactor = currentHeight / mRatioHeight;
+
+ setMeasuredDimension(currentWidth.toInt(), currentHeight.toInt())
+ this.invalidate()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/capturer/view/CapturerFragment.kt b/app/src/main/java/com/suaro/pidroidapp/capturer/view/CapturerFragment.kt
new file mode 100644
index 0000000..b4c98c6
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/capturer/view/CapturerFragment.kt
@@ -0,0 +1,127 @@
+package com.suaro.pidroidapp.capturer.view
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import com.suaro.pidroid.core.FaceDetectionResult
+import com.suaro.pidroidapp.ImageActivity
+import com.suaro.pidroidapp.MainActivity
+import com.suaro.pidroidapp.R
+import com.suaro.pidroidapp.capturer.Capturer
+import com.suaro.pidroidapp.capturer.ImageProcessor
+import com.suaro.pidroidapp.capturer.entity.Circle
+import com.suaro.pidroidapp.capturer.presenter.ImageProcessorPresenter
+import com.suaro.pidroidapp.core.CameraPreviewListener
+
+
+class CapturerFragment : Fragment(), ImageProcessor.View, CameraPreviewListener {
+ private lateinit var capturerView: Capturer.View
+ private lateinit var presenter: ImageProcessorPresenter
+ private var canvasView: CanvasView? = null
+ private var first: Boolean = true
+ private var processing: Boolean = false
+ private var mLock: Any = Object()
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ capturerView = if (context is Capturer.View) {
+ context
+ } else {
+ throw ClassCastException(
+ context.toString()
+ .toString() + " must implement ObInstructions.View"
+ )
+ }
+ //TODO: Fix this...
+ (activity as MainActivity).addCameraPreviewListener(this)
+ }
+
+ override fun onCreate(savedInstance: Bundle?) {
+ super.onCreate(savedInstance)
+ }
+
+ override fun onCreateView(
+ layoutInflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val v: View = layoutInflater.inflate(R.layout.capturer_fragment, container, false)
+ ?: return null
+ setRetainInstance(true)
+ presenter = ImageProcessorPresenter(activity!!, this)
+ canvasView = v.findViewById(R.id.canvas_view)
+ return v
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+
+ }
+
+ override fun onResume() {
+ super.onResume()
+ }
+
+
+ companion object {
+ private val TAG = CapturerFragment::class.java.simpleName
+ fun newInstance(): CapturerFragment {
+ val f = CapturerFragment()
+ //val args = Bundle()
+ //args.putParcelable("instructions_item", item)
+ //f.setArguments(args)
+ return f
+ }
+ }
+
+ override fun onFaceDetectorFinish(info: FaceDetectionResult) {
+ this.activity?.runOnUiThread {
+ synchronized(mLock) {
+ processing = false;
+ canvasView?.faces?.clear()
+ canvasView?.eyes?.clear()
+ canvasView?.landmarks?.clear()
+
+ if (info.detected) {
+ //TODO: Check draw aspect ratio.
+ for (face in info.faces) {
+ var rect: Rect = Rect(
+ face.topLeft.x,
+ face.topLeft.y,
+ face.topLeft.x + face.width,
+ face.topLeft.y + face.height
+ )
+ canvasView?.faces?.add(rect)
+ for (eye in face.eyes) {
+ canvasView?.eyes?.add(Circle(eye.center, eye.radius))
+ }
+
+ for (landmark in face.landmarks) {
+ canvasView?.landmarks?.add(Circle(landmark.center, landmark.radius))
+ }
+ }
+
+ }
+
+ canvasView?.redraw()
+ }
+ }
+ }
+
+ override fun onPreviewFrame(bitmap: Bitmap) {
+ if (first) {
+ canvasView?.setAspectRatio(bitmap.width, bitmap.height)
+ first = false
+ }
+
+ //if (processing)
+ // return
+
+ processing = true
+ this.presenter.detectFace(bitmap)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/core/CameraPreviewListener.kt b/app/src/main/java/com/suaro/pidroidapp/core/CameraPreviewListener.kt
new file mode 100644
index 0000000..3c397fc
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/core/CameraPreviewListener.kt
@@ -0,0 +1,7 @@
+package com.suaro.pidroidapp.core
+
+import android.graphics.Bitmap
+
+interface CameraPreviewListener {
+ fun onPreviewFrame(bitmap: Bitmap);
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/suaro/pidroidapp/core/Utils.kt b/app/src/main/java/com/suaro/pidroidapp/core/Utils.kt
new file mode 100644
index 0000000..d7dde72
--- /dev/null
+++ b/app/src/main/java/com/suaro/pidroidapp/core/Utils.kt
@@ -0,0 +1,101 @@
+package com.suaro.pidroidapp.core
+
+import android.graphics.Bitmap
+import android.graphics.ImageFormat
+import android.graphics.Rect
+import android.graphics.YuvImage
+import android.media.Image
+import java.io.ByteArrayOutputStream
+import java.nio.ByteBuffer
+
+
+class Utils {
+
+ companion object {
+
+ fun getBytes(image: Bitmap): IntArray? {
+ val pixels = IntArray(image.width * image.height * 4)
+ image.getPixels(pixels, 0, image.width, 0, 0, image.width, image.height)
+ return pixels
+ }
+
+ fun bitmapToBgrBytes(image: Bitmap): ByteArray? {
+
+ // calculate how many bytes our image consists of
+ val bytes = image.byteCount
+ val buffer = ByteBuffer.allocate(bytes) // Create a new buffer
+ image.copyPixelsToBuffer(buffer) // Move the byte data to the buffer
+ val temp = buffer.array() // Get the underlying array containing the data.
+ val pixels = ByteArray(temp.size / 4 * 3) // Allocate for 3 byte BGR
+
+ // Copy pixels into place
+ for (i in 0 until temp.size / 4) {
+ pixels[i * 3] = temp[i * 4 + 3] // B
+ pixels[i * 3 + 1] = temp[i * 4 + 2] // G
+ pixels[i * 3 + 2] = temp[i * 4 + 1] // R
+
+ // Alpha is discarded
+ }
+ return pixels
+ }
+
+ fun bitmapToGrayBytes(image: Bitmap): ByteArray? {
+ // calculate how many bytes our image consists of
+ val bytes = image.byteCount
+ val buffer = ByteBuffer.allocate(bytes) // Create a new buffer
+ image.copyPixelsToBuffer(buffer) // Move the byte data to the buffer
+ val temp = buffer.array() // Get the underlying array containing the data.
+ val pixels = ByteArray(temp.size / 4) //RGBA (4 channel) size is 4x greater than Gray (One channel) size
+
+ var b: Double = 0.0
+ var g: Double = 0.0
+ var r: Double = 0.0
+
+ var gray: Double = 0.0
+ // Copy pixels into place
+ for (i in 0 until temp.size / 4) {
+ b = temp[i * 4 + 2].toUByte().toDouble()
+ g = temp[i * 4 + 1].toUByte().toDouble()
+ r = temp[i * 4 + 0].toUByte().toDouble()
+
+ gray = 0.2 * r + 0.7*g + 0.1*b
+
+ pixels[i] = gray.toInt().toByte()
+ }
+ return pixels
+ }
+
+ fun bitmapToByteArray(bmp: Bitmap): ByteArray {
+ val stream = ByteArrayOutputStream()
+ bmp.compress(Bitmap.CompressFormat.PNG, 100, stream)
+ val byteArray: ByteArray = stream.toByteArray()
+ bmp.recycle()
+ return byteArray
+ }
+
+ fun YUV_420_888toNV21(image: Image): ByteArray? {
+ val nv21: ByteArray
+ val yBuffer: ByteBuffer = image.getPlanes().get(0).getBuffer()
+ val uBuffer: ByteBuffer = image.getPlanes().get(1).getBuffer()
+ val vBuffer: ByteBuffer = image.getPlanes().get(2).getBuffer()
+ val ySize: Int = yBuffer.remaining()
+ val uSize: Int = uBuffer.remaining()
+ val vSize: Int = vBuffer.remaining()
+ nv21 = ByteArray(ySize + uSize + vSize)
+
+ //U and V are swapped
+ yBuffer.get(nv21, 0, ySize)
+ vBuffer.get(nv21, ySize, vSize)
+ uBuffer.get(nv21, ySize + vSize, uSize)
+ return nv21
+ }
+
+
+ fun NV21toJPEG(nv21: ByteArray, width: Int, height: Int): ByteArray? {
+ val out = ByteArrayOutputStream()
+ val yuv = YuvImage(nv21, ImageFormat.NV21, width, height, null)
+ yuv.compressToJpeg(Rect(0, 0, width, height), 100, out)
+ return out.toByteArray()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/cuadrado.jpg b/app/src/main/res/drawable/cuadrado.jpg
new file mode 100644
index 0000000..3eb1852
Binary files /dev/null and b/app/src/main/res/drawable/cuadrado.jpg differ
diff --git a/app/src/main/res/drawable/frame.jpg b/app/src/main/res/drawable/frame.jpg
new file mode 100755
index 0000000..488e70c
Binary files /dev/null and b/app/src/main/res/drawable/frame.jpg differ
diff --git a/app/src/main/res/drawable/friends.jpg b/app/src/main/res/drawable/friends.jpg
new file mode 100644
index 0000000..e0da90b
Binary files /dev/null and b/app/src/main/res/drawable/friends.jpg differ
diff --git a/app/src/main/res/drawable/friends2.jpg b/app/src/main/res/drawable/friends2.jpg
new file mode 100644
index 0000000..37eabc4
Binary files /dev/null and b/app/src/main/res/drawable/friends2.jpg differ
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/multi.jpg b/app/src/main/res/drawable/multi.jpg
new file mode 100644
index 0000000..aa95ac7
Binary files /dev/null and b/app/src/main/res/drawable/multi.jpg differ
diff --git a/app/src/main/res/drawable/nayla.jpg b/app/src/main/res/drawable/nayla.jpg
new file mode 100644
index 0000000..a3dfc93
Binary files /dev/null and b/app/src/main/res/drawable/nayla.jpg differ
diff --git a/app/src/main/res/drawable/portrait.jpg b/app/src/main/res/drawable/portrait.jpg
new file mode 100644
index 0000000..4148f08
Binary files /dev/null and b/app/src/main/res/drawable/portrait.jpg differ
diff --git a/app/src/main/res/drawable/ross.jpg b/app/src/main/res/drawable/ross.jpg
new file mode 100644
index 0000000..fc5a5b1
Binary files /dev/null and b/app/src/main/res/drawable/ross.jpg differ
diff --git a/app/src/main/res/drawable/rossout.jpg b/app/src/main/res/drawable/rossout.jpg
new file mode 100755
index 0000000..c18f807
Binary files /dev/null and b/app/src/main/res/drawable/rossout.jpg differ
diff --git a/app/src/main/res/drawable/sample.jpg b/app/src/main/res/drawable/sample.jpg
new file mode 100644
index 0000000..d9f4c01
Binary files /dev/null and b/app/src/main/res/drawable/sample.jpg differ
diff --git a/app/src/main/res/layout/activity_image.xml b/app/src/main/res/layout/activity_image.xml
new file mode 100644
index 0000000..735e172
--- /dev/null
+++ b/app/src/main/res/layout/activity_image.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..bd1c08b
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/camera_fragment.xml b/app/src/main/res/layout/camera_fragment.xml
new file mode 100644
index 0000000..6895115
--- /dev/null
+++ b/app/src/main/res/layout/camera_fragment.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/capturer_fragment.xml b/app/src/main/res/layout/capturer_fragment.xml
new file mode 100644
index 0000000..efce3b4
--- /dev/null
+++ b/app/src/main/res/layout/capturer_fragment.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a571e60
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..61da551
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c41dd28
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..db5080a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..6dba46d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..da31a87
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..15ac681
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..b216f2d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..f25a419
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..e96783c
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..6e2ce11
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..20b5d16
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ PiDroid
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..95d5327
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/suaro/pidroid/ExampleUnitTest.kt b/app/src/test/java/com/suaro/pidroid/ExampleUnitTest.kt
new file mode 100644
index 0000000..1f0c277
--- /dev/null
+++ b/app/src/test/java/com/suaro/pidroid/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.suaro.pidroidapp
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..e524978
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ ext.kotlin_version = "1.4.10"
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:4.1.0"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..98bed16
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..960a14f
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Jan 21 20:28:18 CET 2021
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/pidroid/.gitignore b/pidroid/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/pidroid/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/pidroid/CMakeLists.txt b/pidroid/CMakeLists.txt
new file mode 100644
index 0000000..9b8f5a2
--- /dev/null
+++ b/pidroid/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1)
+
+file(GLOB HEADER_FILES src/main/cpp/*.h src/main/cpp/*.hpp src/main/cpp/pidroid/*.hpp src/main/cpp/pidroid/commons.hpp)
+file(GLOB SOURCE_FILES src/main/cpp/*.cpp src/main/cpp/pidroid/*.cpp src/main/cpp/pidroid/commons.cpp)
+
+add_library(pidroid SHARED ${SOURCE_FILES} ${HEADER_FILES})
+set(CMAKE_VERBOSE_MAKEFILE on)
+
+# include_directories(/home/adrian/Escritorio/AndroidStudioProjects/PiDroid/app/src/main/cpp/include)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
+
+find_library( # Sets the name of the path variable.
+ log-lib
+
+ # Specifies the name of the NDK library that
+ # you want CMake to locate.
+ log )
+
+find_library( # Sets the name of the path variable.
+ android-lib
+
+ # Specifies the name of the NDK library that
+ # you want CMake to locate.
+ android )
+
+target_link_libraries( # Specifies the target library.
+ pidroid
+ ${log-lib}
+ ${android-lib})
+
+target_compile_options(pidroid PRIVATE
+ "$<$:-O3>"
+ "$<$:-O3>"
+ )
\ No newline at end of file
diff --git a/pidroid/build.gradle b/pidroid/build.gradle
new file mode 100644
index 0000000..0d3a1d2
--- /dev/null
+++ b/pidroid/build.gradle
@@ -0,0 +1,67 @@
+plugins {
+ id 'com.android.library'
+ id 'kotlin-android'
+}
+
+android {
+ compileSdkVersion 30
+ buildToolsVersion "30.0.3"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 30
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ externalNativeBuild {
+
+ cmake {
+ cppFlags "-std=c++11 -frtti -fexceptions"
+ abiFilters 'x86', 'x86_64','armeabi-v7a', 'arm64-v8a'
+ }
+
+ ndk {
+ abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
+ }
+ }
+ }
+
+ sourceSets {
+ main {
+ jniLibs.srcDirs = ['src/main/jniLibs/']
+ }
+ }
+ buildTypes {
+ release {
+ minifyEnabled true
+ consumerProguardFiles 'release-proguard.cfg'
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-build.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+ externalNativeBuild {
+ cmake {
+ path file('CMakeLists.txt')
+ }
+ }
+
+}
+
+dependencies {
+
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation 'androidx.core:core-ktx:1.3.2'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'com.google.android.material:material:1.3.0'
+ testImplementation 'junit:junit:4.+'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+}
\ No newline at end of file
diff --git a/pidroid/consumer-rules.pro b/pidroid/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/pidroid/proguard-build.pro b/pidroid/proguard-build.pro
new file mode 100644
index 0000000..72ccb6e
--- /dev/null
+++ b/pidroid/proguard-build.pro
@@ -0,0 +1,30 @@
+# 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
+
+-keep class com.suaro.pidroid.Pidroid {*;}
+-keep class com.suaro.pidroid.core.NativeMethods {*;}
+-keep class com.suaro.pidroid.core.PidroidConfig {*;}
+-keep class com.suaro.pidroid.core.FaceDetectionResult {*;}
+-keep class com.suaro.pidroid.core.Face {*;}
+-keep class com.suaro.pidroid.core.Eye {*;}
+-keep class com.suaro.pidroid.core.Landmark {*;}
+-keep class com.suaro.pidroid.core.Point {*;}
\ No newline at end of file
diff --git a/pidroid/release-proguard.cfg b/pidroid/release-proguard.cfg
new file mode 100755
index 0000000..c5a235b
--- /dev/null
+++ b/pidroid/release-proguard.cfg
@@ -0,0 +1,24 @@
+# ---- REQUIRED card.io CONFIG ----------------------------------------
+# card.io is a native lib, so anything crossing JNI must not be changed
+
+# Don't obfuscate DetectionInfo or public fields, since
+# it is used by native methods
+-keep class com.suaro.pidroid.Pidroid {*;}
+-keep class com.suaro.pidroid.core.NativeMethods {*;}
+-keep class com.suaro.pidroid.core.PidroidConfig {*;}
+-keep class com.suaro.pidroid.core.FaceDetectionResult {*;}
+-keep class com.suaro.pidroid.core.Face {*;}
+-keep class com.suaro.pidroid.core.Eye {*;}
+-keep class com.suaro.pidroid.core.Landmark {*;}
+-keep class com.suaro.pidroid.core.Point {*;}
+
+
+# Don't mess with classes with native methods
+
+-keepclasseswithmembers class * {
+ native ;
+}
+
+-keepclasseswithmembernames class * {
+ native ;
+}
diff --git a/pidroid/src/androidTest/java/com/suaro/pidroid/ExampleInstrumentedTest.kt b/pidroid/src/androidTest/java/com/suaro/pidroid/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..cd1a9e7
--- /dev/null
+++ b/pidroid/src/androidTest/java/com/suaro/pidroid/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.suaro.pidroid
+
+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.suaro.pidroid.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/pidroid/src/main/AndroidManifest.xml b/pidroid/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..be06055
--- /dev/null
+++ b/pidroid/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/pidroid/src/main/assets/facefinder b/pidroid/src/main/assets/facefinder
new file mode 100644
index 0000000..340d0f5
Binary files /dev/null and b/pidroid/src/main/assets/facefinder differ
diff --git a/pidroid/src/main/assets/lp312 b/pidroid/src/main/assets/lp312
new file mode 100644
index 0000000..af33fa7
Binary files /dev/null and b/pidroid/src/main/assets/lp312 differ
diff --git a/pidroid/src/main/assets/lp38 b/pidroid/src/main/assets/lp38
new file mode 100644
index 0000000..ac843d3
Binary files /dev/null and b/pidroid/src/main/assets/lp38 differ
diff --git a/pidroid/src/main/assets/lp42 b/pidroid/src/main/assets/lp42
new file mode 100644
index 0000000..25aef58
Binary files /dev/null and b/pidroid/src/main/assets/lp42 differ
diff --git a/pidroid/src/main/assets/lp44 b/pidroid/src/main/assets/lp44
new file mode 100644
index 0000000..281e441
Binary files /dev/null and b/pidroid/src/main/assets/lp44 differ
diff --git a/pidroid/src/main/assets/lp46 b/pidroid/src/main/assets/lp46
new file mode 100644
index 0000000..4d0ae7d
Binary files /dev/null and b/pidroid/src/main/assets/lp46 differ
diff --git a/pidroid/src/main/assets/lp81 b/pidroid/src/main/assets/lp81
new file mode 100644
index 0000000..f6acf1a
Binary files /dev/null and b/pidroid/src/main/assets/lp81 differ
diff --git a/pidroid/src/main/assets/lp82 b/pidroid/src/main/assets/lp82
new file mode 100644
index 0000000..5210cf7
Binary files /dev/null and b/pidroid/src/main/assets/lp82 differ
diff --git a/pidroid/src/main/assets/lp84 b/pidroid/src/main/assets/lp84
new file mode 100644
index 0000000..b22eb3b
Binary files /dev/null and b/pidroid/src/main/assets/lp84 differ
diff --git a/pidroid/src/main/assets/lp93 b/pidroid/src/main/assets/lp93
new file mode 100644
index 0000000..b131380
Binary files /dev/null and b/pidroid/src/main/assets/lp93 differ
diff --git a/pidroid/src/main/assets/puploc b/pidroid/src/main/assets/puploc
new file mode 100644
index 0000000..0447dcf
Binary files /dev/null and b/pidroid/src/main/assets/puploc differ
diff --git a/pidroid/src/main/cpp/pidroid.cpp b/pidroid/src/main/cpp/pidroid.cpp
new file mode 100644
index 0000000..7fa78af
--- /dev/null
+++ b/pidroid/src/main/cpp/pidroid.cpp
@@ -0,0 +1,418 @@
+
+/*
+ * See the file "LICENSE.md" for the full license governing this code.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include "pidroid/puploc.hpp"
+#include "pidroid/flploc.hpp"
+#include "pidroid/pico.hpp"
+#include
+#include
+#include
+#include
+#include "pidroid.hpp"
+
+#define DEBUG_TAG "pidroid"
+
+
+
+pidroidlib::Pidroid pidroid;
+
+
+
+extern "C"
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
+
+ JNIEnv *env;
+ jint status = vm->GetEnv((void **) &env, JNI_VERSION_1_6);
+ if (status != JNI_OK)
+ return -1;
+
+ int result = pidroid.loadClasses(env);
+
+ if(result != 0) {
+ return result;
+ }
+
+ return JNI_VERSION_1_6;
+}
+
+extern "C"
+JNIEXPORT void JNICALL
+Java_com_suaro_pidroid_core_NativeMethods_00024Companion_setup(JNIEnv *env, jobject thiz, jobject pidroidConfig, jobject assetManager) {
+
+ pidroid = pidroidlib::Pidroid();
+ pidroid.setup(env, thiz, pidroidConfig, assetManager);
+
+}
+
+
+extern "C"
+JNIEXPORT void JNICALL
+Java_com_suaro_pidroid_core_NativeMethods_00024Companion_detectFace(JNIEnv *env, jobject thiz,
+ jintArray rgbaBytes, jint width,
+ jint height,
+ jobject detection_info) {
+
+ pidroid.detectFace(env, thiz, rgbaBytes, width, height, detection_info);
+
+}
+
+uint8_t* pidroidlib::Pidroid::RGBABytesToGrayBytes(JNIEnv *env, const uint8_t* rgbaBytes, int width, int height) {
+ const int size = height * width;
+ int i = -1;
+ float b = 0, g = 0, r = 0, gray = 0;
+ auto * out = new uint8_t[size];
+
+ for(i = 0; i < size; ++i ) {
+ b = rgbaBytes[i * 4 + 2];
+ g = rgbaBytes[i * 4 + 1];
+ r = rgbaBytes[i * 4 + 0];
+ gray = 0.2f* r + 0.7f*g + 0.1f*b;
+ out[i] = int(gray);
+ }
+ return out;
+}
+
+std::vector pidroidlib::Pidroid::arraylistToVectorJObject(JNIEnv *env, jobject arrayList) {
+ jint len = env->CallIntMethod(arrayList, jArrayList.size);
+ std::vector result;
+ result.reserve(len);
+ for (jint i=0; i(env->CallObjectMethod(arrayList, jArrayList.get, i));
+ result.emplace_back(element);
+ env->DeleteLocalRef(element);
+ }
+ return result;
+}
+
+jobject pidroidlib::Pidroid::vectorJObjectToArrayList(JNIEnv *env, std::vector vector) {
+ jobject result = env->NewObject(jArrayList.classRef, jArrayList.init, vector.size());
+ for (jobject element: vector) {
+ env->CallBooleanMethod(result, jArrayList.add, element);
+ env->DeleteLocalRef(element);
+ }
+ return result;
+}
+
+std::vector pidroidlib::Pidroid::readFileFromAssets(JNIEnv *pEnv, AAssetManager *mgr, const char *folder, const char *filename) {
+ AAssetDir* assetDir = AAssetManager_openDir(mgr, folder);
+ const char* currentFilename;
+ std::vector buffer;
+ while ((currentFilename = AAssetDir_getNextFileName(assetDir)) != NULL)
+ {
+ //search for desired file
+ if(!strcmp(currentFilename, filename))
+ {
+ AAsset *asset = AAssetManager_open(mgr, currentFilename, 2);
+
+ //holds size of searched file
+ off64_t length = AAsset_getLength64(asset);
+ //keeps track of remaining bytes to read
+ off64_t remaining = AAsset_getRemainingLength64(asset);
+ size_t Mb = 1000 *1024; // 1Mb is maximum chunk size for compressed assets
+ size_t currChunk;
+ buffer.reserve(length);
+
+ //while we have still some data to read
+ while (remaining != 0)
+ {
+ //set proper size for our next chunk
+ if(remaining >= Mb)
+ {
+ currChunk = Mb;
+ }
+ else
+ {
+ currChunk = remaining;
+ }
+ char chunk[currChunk];
+
+ //read data chunk
+ if(AAsset_read(asset, chunk, currChunk) > 0) // returns less than 0 on error
+ {
+ //and append it to our vector
+ buffer.insert(buffer.end(),chunk, chunk + currChunk);
+ remaining = AAsset_getRemainingLength64(asset);
+ }
+ }
+ AAsset_close(asset);
+ }
+
+ }
+
+ return buffer;
+}
+
+
+int pidroidlib::Pidroid::loadClasses(JNIEnv *env) {
+
+ jclass pointClass = env->FindClass("com/suaro/pidroid/core/Point");
+ if (pointClass == NULL) {
+ return -1;
+ }
+ jPoint.classRef = (jclass) env->NewGlobalRef(pointClass);
+ jPoint.x = env->GetFieldID(pointClass, "x", "I");
+ jPoint.y = env->GetFieldID(pointClass, "y", "I");
+
+ jclass dEyeClass = env->FindClass("com/suaro/pidroid/core/Eye");
+ if (dEyeClass == NULL) {
+ return -1;
+ }
+
+ jEye.classRef = (jclass) env->NewGlobalRef(dEyeClass);
+ jEye.init = env->GetMethodID(dEyeClass, "", "()V");
+ jEye.center = env->GetFieldID(dEyeClass, "center", "Lcom/suaro/pidroid/core/Point;");
+ jEye.radius = env->GetFieldID(dEyeClass, "radius", "I");
+
+ jclass dLandmarkClass = env->FindClass("com/suaro/pidroid/core/Landmark");
+ if (dLandmarkClass == NULL) {
+ return -1;
+ }
+
+ jLandmark.classRef = (jclass) env->NewGlobalRef(dLandmarkClass);
+ jLandmark.init = env->GetMethodID(dLandmarkClass, "", "()V");
+ jLandmark.center = env->GetFieldID(dLandmarkClass, "center", "Lcom/suaro/pidroid/core/Point;");
+ jLandmark.radius = env->GetFieldID(dLandmarkClass, "radius", "I");
+
+ jclass dFaceClass = env->FindClass("com/suaro/pidroid/core/Face");
+ if (dFaceClass == NULL) {
+ return -1;
+ }
+ jFace.classRef = (jclass) env->NewGlobalRef(dFaceClass);
+ jFace.init = env->GetMethodID(dFaceClass, "", "()V");
+ jFace.topLeft = env->GetFieldID(dFaceClass, "topLeft",
+ "Lcom/suaro/pidroid/core/Point;");
+ jFace.width = env->GetFieldID(dFaceClass, "width",
+ "I");
+ jFace.height = env->GetFieldID(dFaceClass, "height",
+ "I");
+ jFace.eyes = env->GetFieldID(dFaceClass, "eyes", "Ljava/util/ArrayList;");
+
+ jFace.landmarks = env->GetFieldID(dFaceClass, "landmarks", "Ljava/util/ArrayList;");
+
+ jclass dArrayList = env->FindClass("java/util/ArrayList");
+ if (dArrayList == NULL) {
+ return -1;
+ }
+ jArrayList.classRef = (jclass) env->NewGlobalRef(dArrayList);
+ jArrayList.init = env->GetMethodID(dArrayList, "", "(I)V");
+ jArrayList.size = env->GetMethodID (dArrayList, "size", "()I");
+ jArrayList.get = env->GetMethodID(dArrayList, "get", "(I)Ljava/lang/Object;");
+ jArrayList.add = env->GetMethodID(dArrayList, "add", "(Ljava/lang/Object;)Z");
+
+ jclass dInfoClass = env->FindClass("com/suaro/pidroid/core/FaceDetectionResult");
+ if (dInfoClass == NULL) {
+ return -1;
+ }
+ jFaceDetectionResult.classRef = (jclass) env->NewGlobalRef(dInfoClass);
+ jFaceDetectionResult.faces = env->GetFieldID(dInfoClass, "faces", "Ljava/util/ArrayList;");
+ jFaceDetectionResult.detected = env->GetFieldID(dInfoClass, "detected", "Z");
+
+ jclass dCascadeClass = env->FindClass("com/suaro/pidroid/core/PidroidConfig");
+ if (dCascadeClass == NULL) {
+ return -1;
+ }
+ jPidroidConfig.classRef = (jclass) env->NewGlobalRef(dCascadeClass);
+ jPidroidConfig.angle = env->GetFieldID(dCascadeClass, "angle", "F");
+ jPidroidConfig.maxsize = env->GetFieldID(dCascadeClass, "maxsize", "I");
+ jPidroidConfig.minsize = env->GetFieldID(dCascadeClass, "minsize", "I");
+ jPidroidConfig.perturbs = env->GetFieldID(dCascadeClass, "perturbs", "I");
+ jPidroidConfig.clustering = env->GetFieldID(dCascadeClass, "clustering", "Z");
+ jPidroidConfig.qthreshold = env->GetFieldID(dCascadeClass, "qthreshold", "F");
+ jPidroidConfig.scalefactor = env->GetFieldID(dCascadeClass, "scalefactor", "F");
+ jPidroidConfig.stridefactor = env->GetFieldID(dCascadeClass, "stridefactor", "F");
+ jPidroidConfig.pupilDetectionEnable = env->GetFieldID(dCascadeClass, "pupilDetectionEnable", "Z");
+ jPidroidConfig.landmarkDetectionEnable = env->GetFieldID(dCascadeClass, "landmarkDetectionEnable", "Z");
+ jPidroidConfig.prominentFaceOnly = env->GetFieldID(dCascadeClass, "prominentFaceOnly", "Z");
+
+ return 0;
+}
+
+void pidroidlib::Pidroid::detectFace(JNIEnv *env, jobject thiz, jintArray rgbaBytes, jint width,
+ jint height, jobject detection_info) {
+
+ uint8_t* pixels;
+ uint8_t* rgbaPixels;
+ int nrows, ncols, ldim;
+
+
+ std::vector faceArray = std::vector();
+ std::vector eyeArray = std::vector();
+ std::vector landmarkArray = std::vector();
+
+ nrows = height;
+ ncols = width;
+ ldim = width;
+
+ jint* dataPtr = (*env).GetIntArrayElements(rgbaBytes, NULL);
+
+ rgbaPixels = (uint8_t*)dataPtr;
+ pixels = RGBABytesToGrayBytes(env, rgbaPixels, ncols, nrows);
+
+ pidroidlib::PicoResult res{};
+
+ auto start_time = std::chrono::high_resolution_clock::now();
+ res = picol.RunCascade(nrows, ncols, pixels, ldim);
+ res = picol.updateMemory(res);
+
+ if(clustering) {
+ res = pidroidlib::Pico::clusterDetections(res, 0.2);
+ }
+
+ if(res.ndetections > 0 && prominentFaceOnly) {
+ res = pidroidlib::Pico::getProminentFace(res);
+ }
+
+ pidroidlib::CascadeParams params = picol.getCascadeParams();
+
+ std::vector eyes = std::vector();
+ pidroidlib::FlpDetection landmarks = {};
+
+ for(int i=0; i=params.minThreshold) {
+ int centerX = det.col;
+ int centerY = det.row;
+ int radius = det.scale / 2;
+
+ if (radius > 50 && pupilDetectionEnable) {
+ pidroidlib::PuplocDetection left = {
+ .row = centerY - int(0.075*radius),
+ .col = centerX- int(0.175*radius),
+ .scale = int(det.scale * 0.35f),
+ .perturbs = perturbs
+ };
+ left = puploc1.runDetector(left, nrows, ncols, pixels, ldim, params.angle, false);
+
+ pidroidlib::PuplocDetection right = {
+ .row = centerY - int(0.075*radius),
+ .col = centerX + int(0.175*radius),
+ .scale = int(det.scale * 0.35f),
+ .perturbs = perturbs
+ };
+ right = puploc1.runDetector(right, nrows, ncols, pixels, ldim, params.angle, false);
+
+
+ eyes.push_back(left);
+ right.scale = left.scale;
+ eyes.push_back(right);
+
+ if(landmarkDetectionEnable) {
+ landmarks = flploc.getLandmarkPoints(left, right, nrows, ncols, pixels, ldim, perturbs);
+ }
+ }
+
+ for(auto &&eye: eyes) {
+ jobject faceEye = env->NewObject(jEye.classRef, jEye.init);
+ jobject centerEyes = env->GetObjectField(faceEye, jEye.center);
+
+ env->SetIntField(centerEyes, jPoint.x, eye.col);
+ env->SetIntField(centerEyes, jPoint.y, eye.row);
+
+ env->SetObjectField(faceEye, jEye.center, centerEyes);
+ env->SetIntField(faceEye, jEye.radius, int(eye.scale * 0.2f));
+
+ eyeArray.push_back(faceEye);
+ }
+
+ for(auto &&landmark: landmarks.detections) {
+ jobject faceLandmark = env->NewObject(jLandmark.classRef, jLandmark.init);
+ jobject centerLandmark = env->GetObjectField(faceLandmark, jLandmark.center);
+
+ env->SetIntField(centerLandmark, jPoint.x, landmark.col);
+ env->SetIntField(centerLandmark, jPoint.y, landmark.row);
+
+ env->SetObjectField(faceLandmark, jLandmark.center, centerLandmark);
+ env->SetIntField(faceLandmark, jLandmark.radius, int(landmark.scale * 0.15f));
+
+ landmarkArray.push_back(faceLandmark);
+ }
+
+
+
+ int faceWidth = radius * 2;
+ int faceHeight = faceWidth; // 1.58 is Habitual Face Width-Height Ratio
+ int x = centerX - (faceWidth / 2);
+ int y = centerY - (faceHeight / 2);
+
+ jobject face = env->NewObject(jFace.classRef, jFace.init);
+ jobject topLeft = env->GetObjectField(face, jFace.topLeft);
+
+ env->SetIntField(topLeft, jPoint.x, x);
+ env->SetIntField(topLeft, jPoint.y, y);
+ env->SetObjectField(face, jFace.topLeft, topLeft);
+
+ env->SetIntField(face, jFace.width, faceWidth);
+ env->SetIntField(face, jFace.height, faceHeight);
+
+ env->SetObjectField(face, jFace.eyes, vectorJObjectToArrayList(env, eyeArray));
+ env->SetObjectField(face, jFace.landmarks, vectorJObjectToArrayList(env, landmarkArray));
+
+ faceArray.push_back(face);
+ }
+ }
+
+ env->SetObjectField(detection_info, jFaceDetectionResult.faces, vectorJObjectToArrayList(env, faceArray));
+ env->SetBooleanField(detection_info, jFaceDetectionResult.detected, faceArray.size() > 0);
+
+ auto end_time = std::chrono::high_resolution_clock::now();
+ auto duration = std::chrono::duration_cast(end_time-start_time).count();
+ std::string logText = "Processing time: "+std::to_string(duration);
+
+ __android_log_write(ANDROID_LOG_VERBOSE, "Detection", logText.c_str());
+
+ faceArray.clear();
+ eyeArray.clear();
+ landmarkArray.clear();
+}
+
+void
+pidroidlib::Pidroid::setup(JNIEnv *env, jobject thiz, jobject pidroidConfig, jobject assetManager) {
+ pidroidlib::CascadeParams cascadeParams = {
+ .minSize = env->GetIntField(pidroidConfig, jPidroidConfig.minsize),
+ .maxSize = env->GetIntField(pidroidConfig, jPidroidConfig.maxsize),
+ .shiftFactor = env->GetFloatField(pidroidConfig, jPidroidConfig.stridefactor),
+ .scaleFactor = env->GetFloatField(pidroidConfig, jPidroidConfig.scalefactor),
+ .angle = env->GetFloatField(pidroidConfig, jPidroidConfig.angle),
+ .minThreshold = env->GetFloatField(pidroidConfig, jPidroidConfig.qthreshold)
+ };
+
+ perturbs = env->GetIntField(pidroidConfig, jPidroidConfig.perturbs);
+ clustering = env->GetBooleanField(pidroidConfig, jPidroidConfig.clustering);
+ pupilDetectionEnable = env->GetBooleanField(pidroidConfig, jPidroidConfig.pupilDetectionEnable);
+ landmarkDetectionEnable = env->GetBooleanField(pidroidConfig, jPidroidConfig.landmarkDetectionEnable);
+ prominentFaceOnly = env->GetBooleanField(pidroidConfig, jPidroidConfig.prominentFaceOnly);
+
+ AAssetManager *mgr = AAssetManager_fromJava(env, assetManager);
+
+ std::vector facefinder = readFileFromAssets(env, mgr, "", "facefinder");
+ std::vector puploc = readFileFromAssets(env, mgr, "", "puploc");
+
+ picol = pidroidlib::Pico(cascadeParams);
+ picol.unpackPicoCascade(facefinder);
+
+ puploc1 = pidroidlib::Puploc();
+ puploc1.unpackCascade(puploc);
+
+ //TODO: Improve flp reads.
+ std::vector flpCascades = {"lp38", "lp42", "lp44",
+ "lp46", "lp81", "lp82",
+ "lp84", "lp93", "lp312"};
+
+ for(const auto& cascadeName : flpCascades) {
+ flploc.addCascade(cascadeName, readFileFromAssets(env, mgr, "", cascadeName.c_str()));
+ }
+}
diff --git a/pidroid/src/main/cpp/pidroid.hpp b/pidroid/src/main/cpp/pidroid.hpp
new file mode 100644
index 0000000..47f98a8
--- /dev/null
+++ b/pidroid/src/main/cpp/pidroid.hpp
@@ -0,0 +1,91 @@
+//
+// Created by adrian on 23/02/21.
+//
+#include
+
+namespace pidroidlib {
+ static struct {
+ jclass classRef;
+ jfieldID minsize;
+ jfieldID maxsize;
+ jfieldID angle;
+ jfieldID scalefactor;
+ jfieldID stridefactor;
+ jfieldID qthreshold;
+ jfieldID perturbs;
+ jfieldID clustering;
+ jfieldID pupilDetectionEnable;
+ jfieldID landmarkDetectionEnable;
+ jfieldID prominentFaceOnly;
+ } jPidroidConfig;
+
+
+ static struct {
+ jclass classRef;
+ jmethodID init;
+ jfieldID topLeft;
+ jfieldID width;
+ jfieldID height;
+ jfieldID eyes;
+ jfieldID landmarks;
+ } jFace;
+
+
+ static struct {
+ jclass classRef;
+ jmethodID init;
+ jmethodID size;
+ jmethodID add;
+ jmethodID get;
+ } jArrayList;
+
+ static struct {
+ jclass classRef;
+ jfieldID faces;
+ jfieldID detected;
+ } jFaceDetectionResult;
+
+ static struct {
+ jclass classRef;
+ jfieldID x;
+ jfieldID y;
+ } jPoint;
+
+ static struct {
+ jclass classRef;
+ jfieldID center;
+ jfieldID radius;
+ jmethodID init;
+ } jEye;
+
+ static struct {
+ jclass classRef;
+ jfieldID center;
+ jfieldID radius;
+ jmethodID init;
+ } jLandmark;
+
+ class Pidroid {
+ private:
+ Puploc puploc1;
+ Pico picol;
+ Flploc flploc;
+
+ bool pupilDetectionEnable = false;
+ bool landmarkDetectionEnable = false;
+ bool prominentFaceOnly = false;
+ bool clustering = true;
+ int perturbs = 50;
+
+ static std::vector arraylistToVectorJObject(JNIEnv *env, jobject arrayList);
+ static uint8_t* RGBABytesToGrayBytes(JNIEnv *env, const uint8_t* rgbaBytes, int width, int height);
+ static jobject vectorJObjectToArrayList(JNIEnv *env, std::vector vector);
+ static std::vector readFileFromAssets(JNIEnv *pEnv, AAssetManager *mgr,const char *folder, const char *filename);
+ public:
+ Pidroid() = default;
+ ~Pidroid() = default;
+ int loadClasses(JNIEnv *env);
+ void setup(JNIEnv *env, jobject thiz, jobject pidroidConfig, jobject assetManager);
+ void detectFace(JNIEnv *env, jobject thiz, jintArray rgbaBytes, jint width, jint height, jobject detection_info);
+ };
+}
diff --git a/pidroid/src/main/cpp/pidroid/commons.cpp b/pidroid/src/main/cpp/pidroid/commons.cpp
new file mode 100644
index 0000000..8cd37d7
--- /dev/null
+++ b/pidroid/src/main/cpp/pidroid/commons.cpp
@@ -0,0 +1,40 @@
+//
+// Created by adrian on 21/02/21.
+//
+
+#include "commons.hpp"
+
+namespace pidroidlib {
+
+ int Commons::buffToInteger(std::vector buffer) {
+ int a = static_cast(static_cast(buffer[0]) << 24 |
+ static_cast(buffer[1]) << 16 |
+ static_cast(buffer[2]) << 8 |
+ static_cast(buffer[3]));
+ return a;
+ }
+
+
+ float Commons::bytesToFloatLittleEndian(std::vector buffer) {
+ float output;
+
+ *((unsigned char *) (&output) + 3) = buffer[0];
+ *((unsigned char *) (&output) + 2) = buffer[1];
+ *((unsigned char *) (&output) + 1) = buffer[2];
+ *((unsigned char *) (&output) + 0) = buffer[3];
+
+ return output;
+ }
+
+
+ float Commons::bytesToFloatBigEndian(std::vector buffer) {
+ float output;
+
+ *((unsigned char *) (&output) + 3) = buffer[3];
+ *((unsigned char *) (&output) + 2) = buffer[2];
+ *((unsigned char *) (&output) + 1) = buffer[1];
+ *((unsigned char *) (&output) + 0) = buffer[0];
+
+ return output;
+ }
+}
\ No newline at end of file
diff --git a/pidroid/src/main/cpp/pidroid/commons.hpp b/pidroid/src/main/cpp/pidroid/commons.hpp
new file mode 100644
index 0000000..af446a8
--- /dev/null
+++ b/pidroid/src/main/cpp/pidroid/commons.hpp
@@ -0,0 +1,19 @@
+//
+// Created by adrian on 21/02/21.
+//
+
+#pragma once
+#include
+
+using namespace std;
+
+namespace pidroidlib {
+ class Commons {
+ public:
+ static int buffToInteger(std::vector buffer);
+
+ static float bytesToFloatLittleEndian(std::vector buffer);
+
+ static float bytesToFloatBigEndian(std::vector buffer);
+ };
+}
\ No newline at end of file
diff --git a/pidroid/src/main/cpp/pidroid/flploc.cpp b/pidroid/src/main/cpp/pidroid/flploc.cpp
new file mode 100644
index 0000000..e8ea41f
--- /dev/null
+++ b/pidroid/src/main/cpp/pidroid/flploc.cpp
@@ -0,0 +1,81 @@
+//
+// Created by adrian on 24/01/21.
+//
+
+#include "flploc.hpp"
+
+
+namespace pidroidlib {
+
+ void Flploc::addCascade(std::string cascadeName, std::vector packet) {
+ Puploc puploc = Puploc();
+ puploc.unpackCascade(std::move(packet));
+ this->cascade.puplocCascade.insert(std::make_pair(cascadeName, puploc));
+ }
+
+ FlpDetection
+ Flploc::getLandmarkPoints(PuplocDetection leftEye, PuplocDetection rightEye, int nrows,
+ int ncols, const uint8_t *pixels, int dim, int perturb) {
+
+ std::vector landmarks = std::vector();
+ Puploc lpc;
+
+ for (const auto &cascadeName : this->eyeCascades) {
+ lpc = this->cascade.puplocCascade.at(cascadeName);
+ landmarks.push_back(
+ pidroidlib::Flploc::getLandmarkPoint(lpc, leftEye, rightEye, nrows, ncols, pixels, dim,
+ perturb, false));
+ landmarks.push_back(
+ pidroidlib::Flploc::getLandmarkPoint(lpc, leftEye, rightEye, nrows, ncols, pixels, dim,
+ perturb, true));
+ }
+
+ for (const auto &cascadeName : this->mouthCascades) {
+ lpc = this->cascade.puplocCascade.at(cascadeName);
+ landmarks.push_back(
+ pidroidlib::Flploc::getLandmarkPoint(lpc, leftEye, rightEye, nrows, ncols, pixels, dim,
+ perturb, false));
+ }
+
+ lpc = this->cascade.puplocCascade.at("lp84");
+ landmarks.push_back(
+ pidroidlib::Flploc::getLandmarkPoint(lpc, leftEye, rightEye, nrows, ncols, pixels, dim, perturb,
+ true));
+
+ return {
+ .detections = landmarks
+ };
+ }
+
+ PuplocDetection Flploc::getLandmarkPoint(const Puploc& detector, PuplocDetection leftEye,
+ PuplocDetection rightEye, int nrows,
+ int ncols, const uint8_t *pixels, int dim, int perturb,
+ bool flipV) {
+
+
+ int dist1 = (leftEye.row - rightEye.row) * (leftEye.row - rightEye.row);
+ int dist2 = (leftEye.col - rightEye.col) * (leftEye.col - rightEye.col);
+ float dist = sqrt(float(dist1 + dist2));
+
+ auto row = float((leftEye.row + rightEye.row) / 2.0 + 0.25 * dist);
+ auto col = float((leftEye.col + rightEye.col) / 2.0 + 0.15 * dist);
+ auto scale = 3.0f * dist;
+
+ PuplocDetection flploc = {
+ .row = int(row),
+ .col = int(col),
+ .scale = int(scale),
+ .perturbs = perturb
+ };
+
+ return detector.runDetector(flploc, nrows, ncols, pixels, dim, 0.0, flipV);
+ }
+
+ Flploc::Flploc() {
+ this->eyeCascades = {"lp46", "lp44", "lp42", "lp38", "lp312"};
+ this->mouthCascades = {"lp93", "lp84", "lp82", "lp81"};
+ }
+
+ Flploc::~Flploc() = default;
+}
+
diff --git a/pidroid/src/main/cpp/pidroid/flploc.hpp b/pidroid/src/main/cpp/pidroid/flploc.hpp
new file mode 100644
index 0000000..912ee2d
--- /dev/null
+++ b/pidroid/src/main/cpp/pidroid/flploc.hpp
@@ -0,0 +1,33 @@
+//
+// Created by adrian on 24/01/21.
+//
+#pragma once
+#include
+#include
+#include