@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,77 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.cpm.india.cameraai"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
ndk {
|
||||
abiFilters.add("armeabi-v7a")
|
||||
abiFilters.add("arm64-v8a")
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
// compileOptions {
|
||||
// sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
// targetCompatibility = JavaVersion.VERSION_1_8
|
||||
// }
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
// jvmTarget = "1.8"
|
||||
jvmTarget = "17"
|
||||
// jvmToolchain(17) // Set this
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
//noinspection UseTomlInstead
|
||||
implementation ("androidx.core:core-ktx:1.0.2")
|
||||
implementation ("androidx.appcompat:appcompat:1.3.1")
|
||||
implementation ("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation ("com.google.android.material:material:1.4.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.2.1")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
|
||||
///camerax
|
||||
val camerax_version = "1.1.0-beta02"
|
||||
|
||||
// val camerax_version = "1.5.0-alpha01"
|
||||
implementation ("androidx.camera:camera-core:$camerax_version")
|
||||
implementation ("androidx.camera:camera-camera2:$camerax_version")
|
||||
implementation ("androidx.camera:camera-lifecycle:$camerax_version")
|
||||
implementation ("androidx.camera:camera-view:$camerax_version")
|
||||
///for alert
|
||||
//implementation ("org.jetbrains.anko:anko-common:0.10.4")
|
||||
//for toast msg
|
||||
implementation ("com.github.GrenderG:Toasty:1.4.2")
|
||||
// mlkit face detection & gson
|
||||
implementation ("com.google.mlkit:face-detection:16.1.7")
|
||||
//google gson
|
||||
implementation ("com.google.code.gson:gson:2.10.1")
|
||||
implementation("org.opencv:opencv:4.9.0")
|
||||
implementation ("androidx.activity:activity-ktx:1.8.0")
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.cpm.india.cameraai
|
||||
|
||||
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.cpm.india.cameraai.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-feature android:name="android.hardware.camera.any" />
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_CAMERA" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
android:required="false" />
|
||||
|
||||
|
||||
<application
|
||||
android:supportsRtl="true"
|
||||
android:allowBackup="false"
|
||||
tools:replace="android:allowBackup">
|
||||
|
||||
<activity
|
||||
android:name=".camera.CameraActivity"
|
||||
android:screenOrientation="portrait"
|
||||
tools:ignore="DiscouragedApi,LockedOrientationActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".preview.PreviewActivity"
|
||||
android:screenOrientation="portrait"
|
||||
tools:ignore="DiscouragedApi,LockedOrientationActivity"
|
||||
android:exported="false" />
|
||||
|
||||
|
||||
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,421 @@
|
||||
package com.cpm.india.cameraai.camera
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.ContentValues.TAG
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.animation.AlphaAnimation
|
||||
import android.view.animation.Animation
|
||||
//import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.ExperimentalGetImage
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.ImageCapture
|
||||
import androidx.camera.core.ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
|
||||
import androidx.camera.core.ImageCapture.FLASH_MODE_AUTO
|
||||
import androidx.camera.core.ImageCaptureException
|
||||
import androidx.camera.core.ImageProxy
|
||||
import androidx.camera.core.Preview
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.cpm.india.cameraai.R
|
||||
import com.cpm.india.cameraai.databinding.ActivityCameraBinding
|
||||
import com.cpm.india.cameraai.preview.PreviewActivity
|
||||
import com.cpm.india.cameraai.utils.GetProperImageRotation.checkBlinkAndMovement
|
||||
import com.cpm.india.cameraai.utils.Utils.calculateBrightness
|
||||
import com.cpm.india.cameraai.utils.Utils.checkBlurriness
|
||||
import com.cpm.india.cameraai.utils.Utils.eyeBlink
|
||||
import com.cpm.india.cameraai.utils.Utils.isFaceTooFar
|
||||
import com.cpm.india.cameraai.utils.Utils.isValidHumanFace
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import com.google.mlkit.vision.face.Face
|
||||
import com.google.mlkit.vision.face.FaceDetection
|
||||
import com.google.mlkit.vision.face.FaceDetectorOptions
|
||||
import com.google.mlkit.vision.face.FaceLandmark
|
||||
//import org.jetbrains.anko.toast
|
||||
import android.widget.Toast
|
||||
import org.opencv.android.OpenCVLoader
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
class CameraActivity : AppCompatActivity() {
|
||||
private var imageCapture: ImageCapture? = null
|
||||
private lateinit var cameraExecutor: ExecutorService
|
||||
private lateinit var binding: ActivityCameraBinding
|
||||
private var flipX = true
|
||||
private var focusFlag = 0
|
||||
var cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
|
||||
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
|
||||
private val REQUEST_CODE_PERMISSIONS = 10
|
||||
private var doubleBackToExitPressedOnce = false
|
||||
private var mainThreadIsStopped: Boolean = true
|
||||
private var parameterThread: Thread? = null
|
||||
private var pictureThread: Thread? = null
|
||||
|
||||
private var filePath: String? = null
|
||||
private var isGrid: Boolean? = false
|
||||
private var isCheckFace: Boolean? = true
|
||||
private var isFacingFront: Boolean? = true
|
||||
private var isFlash = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// enableEdgeToEdge()
|
||||
binding = ActivityCameraBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.main) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
if (!OpenCVLoader.initLocal()) {
|
||||
Log.e("OpenCv", "OpenCV initialization failed")
|
||||
} else {
|
||||
Log.d("OpenCv", "OpenCV initialization succeeded")
|
||||
}
|
||||
eyeBlink = false
|
||||
filePath = intent.getStringExtra("filePath")
|
||||
isGrid = intent.getBooleanExtra("isGrid", false)
|
||||
isCheckFace = intent.getBooleanExtra("isCheckFace", false)
|
||||
isFacingFront = intent.getBooleanExtra("isFacingFront", true)
|
||||
Log.e("filePath", filePath!!)
|
||||
if (!isFacingFront!!) cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||
if (isCheckFace!!) binding.capturebtnCameraActivity.visibility = View.INVISIBLE
|
||||
switchCamera()
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
if (allPermissionsGranted()) {
|
||||
startCamera()
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(
|
||||
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
|
||||
)
|
||||
}
|
||||
focusingContinuous()
|
||||
requestPermissionsforApp()
|
||||
calledOnClick()
|
||||
}
|
||||
|
||||
private fun focusingContinuous() {
|
||||
focusFlag = 1
|
||||
this@CameraActivity.runOnUiThread {
|
||||
runOnUiThread {
|
||||
val anim = AlphaAnimation(0.0f, 1.0f)
|
||||
anim.duration = 10//You can manage the time of the blink with this parameter
|
||||
anim.startOffset = 300
|
||||
anim.repeatMode = Animation.REVERSE
|
||||
anim.repeatCount = Animation.INFINITE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun calledOnClick() {
|
||||
Log.d("Jeevanpclick", filePath.toString())
|
||||
binding.capturebtnCameraActivity.setOnClickListener {
|
||||
val outputOptions = ImageCapture.OutputFileOptions.Builder(File(filePath!!)).build()
|
||||
mainThreadIsStopped = true
|
||||
imageCapture?.takePicture(
|
||||
outputOptions,
|
||||
ContextCompat.getMainExecutor(this),
|
||||
object : ImageCapture.OnImageSavedCallback {
|
||||
override fun onError(exception: ImageCaptureException) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
|
||||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||
mainThreadIsStopped = true
|
||||
pictureThread = thread(start = true) {
|
||||
val savedUri = outputFileResults.savedUri
|
||||
Log.d("Jeevanp", savedUri.toString())
|
||||
val bitmap = BitmapFactory.decodeFile(savedUri?.path)
|
||||
val openCvResult = checkBlurriness(bitmap)
|
||||
val brightnessResult = calculateBrightness(bitmap)
|
||||
if (openCvResult == 1 && brightnessResult.absoluteValue > 0.20) {
|
||||
Log.d("Jeevanp", "1")
|
||||
val intent =
|
||||
Intent(this@CameraActivity, PreviewActivity::class.java)
|
||||
intent.putExtra("photoUri", File(filePath!!).absolutePath)
|
||||
resultLauncher.launch(intent)
|
||||
} else {
|
||||
if (openCvResult != 1 && brightnessResult.absoluteValue <= 0.32) {
|
||||
Log.d("Jeevanp", "2")
|
||||
// runOnUiThread {
|
||||
// toast("Image is blurry and dark. Please try again.")
|
||||
// }
|
||||
|
||||
runOnUiThread {
|
||||
Toast.makeText(applicationContext, "Image is blurry. and dark. Please try again", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
|
||||
|
||||
} else if (brightnessResult.absoluteValue <= 0.32) {
|
||||
Log.d("Jeevanp", "3")
|
||||
runOnUiThread {
|
||||
// toast("Image is dark. Please try again.")
|
||||
Toast.makeText(applicationContext, "Image is dark. Please try again.", Toast.LENGTH_SHORT).show()
|
||||
|
||||
}
|
||||
} else {
|
||||
Log.d("Jeevanp", "4")
|
||||
runOnUiThread {
|
||||
// toast("Image is blurry. Please try again.")
|
||||
Toast.makeText(applicationContext, "Image is blurry. Please try again.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private var resultLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
// There are no request codes
|
||||
val data: Intent? = result.data
|
||||
if (data != null) {
|
||||
val intent = Intent()
|
||||
intent.putExtra("filePath", data.getStringExtra("filePath").toString())
|
||||
setResult(RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchCamera() {
|
||||
binding.switchCamera.setOnClickListener {
|
||||
if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {
|
||||
cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
|
||||
flipX = true
|
||||
} else {
|
||||
cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||
flipX = false
|
||||
}
|
||||
startCamera()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startCamera() {
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||
cameraProviderFuture.addListener({
|
||||
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
|
||||
val preview = Preview.Builder().build()
|
||||
.also {
|
||||
it.setSurfaceProvider(binding.texture.surfaceProvider)
|
||||
//it.surfaceProvider = binding.texture.surfaceProvider
|
||||
}
|
||||
imageCapture = ImageCapture.Builder()
|
||||
.setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY)
|
||||
.setFlashMode(FLASH_MODE_AUTO)
|
||||
.setJpegQuality(100)
|
||||
.build()
|
||||
|
||||
val imageAnalyzer = ImageAnalysis.Builder()
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.build()
|
||||
.also {
|
||||
it.setAnalyzer(
|
||||
cameraExecutor,
|
||||
LuminosityAnalyzer(binding, isCheckFace!!, context = this)
|
||||
)
|
||||
}
|
||||
try {
|
||||
cameraProvider.unbindAll()
|
||||
val cam = cameraProvider.bindToLifecycle(
|
||||
this, cameraSelector, preview, imageCapture, imageAnalyzer
|
||||
)
|
||||
binding.ivFlash.setOnClickListener {
|
||||
if (cam.cameraInfo.hasFlashUnit()) {
|
||||
if (!isFlash) {
|
||||
isFlash = true
|
||||
cam.cameraControl.enableTorch(true) // or false
|
||||
binding.ivFlash.setImageResource(R.mipmap.ic_flash_on_white_24dp)
|
||||
} else {
|
||||
isFlash = false
|
||||
cam.cameraControl.enableTorch(false) // or false
|
||||
binding.ivFlash.setImageResource(R.mipmap.ic_flash_off_white_24dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (exc: Exception) {
|
||||
Log.e(TAG, "Use case binding failed", exc)
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(this))
|
||||
}
|
||||
|
||||
private fun requestPermissionsforApp() {
|
||||
ActivityCompat.requestPermissions(
|
||||
this@CameraActivity,
|
||||
arrayOf(Manifest.permission.CAMERA),
|
||||
2
|
||||
)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int, permissions: Array<String>, grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == REQUEST_CODE_PERMISSIONS) {
|
||||
if (allPermissionsGranted()) {
|
||||
startCamera()
|
||||
} else {
|
||||
runOnUiThread {
|
||||
// toast("Permissions not granted by the user.")
|
||||
Toast.makeText(applicationContext, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show()
|
||||
|
||||
}
|
||||
this@CameraActivity.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
|
||||
ContextCompat.checkSelfPermission(
|
||||
baseContext, it
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
public class LuminosityAnalyzer(
|
||||
val binding: ActivityCameraBinding,
|
||||
val isCheckFace: Boolean,
|
||||
val context: Context
|
||||
) :
|
||||
ImageAnalysis.Analyzer {
|
||||
private fun ByteBuffer.toByteArray(): ByteArray {
|
||||
rewind() // Rewind the buffer to zero
|
||||
val data = ByteArray(remaining())
|
||||
get(data) // Copy the buffer into a byte array
|
||||
return data // Return the byte array
|
||||
}
|
||||
|
||||
@androidx.annotation.OptIn(ExperimentalGetImage::class)
|
||||
override fun analyze(image: ImageProxy) {
|
||||
val inputImage = InputImage.fromMediaImage(
|
||||
image.image!!,
|
||||
image.imageInfo.rotationDegrees
|
||||
)
|
||||
val options = FaceDetectorOptions.Builder()
|
||||
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
|
||||
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL) // Detect landmarks (eyes, mouth, etc.)
|
||||
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL) // Enable face classification (e.g., smiling, eyes open)
|
||||
.setMinFaceSize(0.1f) // Detect smaller faces in the image
|
||||
.enableTracking() // Track faces across frames
|
||||
.build()
|
||||
val faceDetector = FaceDetection.getClient(options)
|
||||
faceDetector.process(inputImage)
|
||||
.addOnSuccessListener { faces: List<Face?>? ->
|
||||
onSuccessListener(
|
||||
faces,
|
||||
inputImage,
|
||||
binding,
|
||||
isCheckFace,
|
||||
context
|
||||
)
|
||||
}.addOnFailureListener {
|
||||
}.addOnCompleteListener { image.close() }
|
||||
}
|
||||
|
||||
@SuppressLint("ResourceType", "UseCompatLoadingForDrawables")
|
||||
fun onSuccessListener(
|
||||
faces: List<Face?>?,
|
||||
inputImage: InputImage,
|
||||
binding: ActivityCameraBinding,
|
||||
isCheckFace: Boolean,
|
||||
context: Context
|
||||
) {
|
||||
if (isCheckFace) {
|
||||
if (!faces.isNullOrEmpty()) {
|
||||
for (face in faces) {
|
||||
val boundingBox = face?.boundingBox
|
||||
// Ensure detected face meets conditions that typically indicate a human face
|
||||
val leftEye = face?.getLandmark(FaceLandmark.LEFT_EYE)
|
||||
val rightEye = face?.getLandmark(FaceLandmark.RIGHT_EYE)
|
||||
val nose = face?.getLandmark(FaceLandmark.NOSE_BASE)
|
||||
val mouthLeft = face?.getLandmark(FaceLandmark.MOUTH_LEFT)
|
||||
val mouthRight = face?.getLandmark(FaceLandmark.MOUTH_RIGHT)
|
||||
if (checkBlinkAndMovement(face!!)) {
|
||||
eyeBlink = true
|
||||
}
|
||||
// Check if the face is too far based on the bounding box size
|
||||
Log.e("eyeBlink", "$eyeBlink")
|
||||
// Example condition: only consider it a human face if the eyes and mouth probabilities are detected
|
||||
if (leftEye != null && rightEye != null && nose != null && mouthLeft != null && mouthRight != null) {
|
||||
if (isValidHumanFace(face) && !isFaceTooFar(boundingBox) && eyeBlink) {
|
||||
Log.e("facedata", "Face detected")
|
||||
binding.tvImageBlurr.setText(R.string.face_detected)
|
||||
binding.capturebtnCameraActivity.visibility = View.VISIBLE
|
||||
} else {
|
||||
Log.e("facedata", "No Face detected")
|
||||
binding.tvImageBlurr.setText(R.string.no_face_detected)
|
||||
binding.capturebtnCameraActivity.visibility = View.INVISIBLE
|
||||
}
|
||||
} else {
|
||||
Log.e("facedata", "No Face detected")
|
||||
binding.tvImageBlurr.setText(R.string.no_face_detected)
|
||||
binding.capturebtnCameraActivity.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e("facedata", "No Face detected")
|
||||
binding.tvImageBlurr.setText(R.string.no_face_detected)
|
||||
binding.capturebtnCameraActivity.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
focusFlag = 0
|
||||
eyeBlink = false
|
||||
mainThreadIsStopped = false
|
||||
binding.capturebtnCameraActivity.isEnabled = true
|
||||
parameterThread = thread(start = true) {
|
||||
this@CameraActivity.runOnUiThread {
|
||||
binding.infoPanel.text = "Please stand in a well lit area"
|
||||
}
|
||||
Thread.sleep(1000)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
mainThreadIsStopped = true
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Runtime.getRuntime().gc()
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (doubleBackToExitPressedOnce) {
|
||||
super.onBackPressed()
|
||||
return
|
||||
}
|
||||
this.doubleBackToExitPressedOnce = true
|
||||
// toast("Please click BACK again to exit")
|
||||
Toast.makeText(applicationContext, "Please click BACK again to exit", Toast.LENGTH_SHORT).show()
|
||||
Handler().postDelayed({
|
||||
doubleBackToExitPressedOnce = false
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.cpm.india.cameraai.preview
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.cpm.india.cameraai.databinding.ActivityPreviewBinding
|
||||
import com.cpm.india.cameraai.utils.GetProperImageRotation
|
||||
import java.io.File
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class PreviewActivity : AppCompatActivity() {
|
||||
private lateinit var cameraExecutor: ExecutorService
|
||||
private lateinit var binding: ActivityPreviewBinding
|
||||
private var photoUri: String? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityPreviewBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
photoUri = intent.getStringExtra("photoUri")
|
||||
val photoFile = photoUri?.let { File(it) }
|
||||
if (photoUri != null) {
|
||||
Log.d("uridata", photoUri!!)
|
||||
}
|
||||
|
||||
val rotatedImageFile = GetProperImageRotation.getRotatedImageFile(photoFile, photoUri, this)
|
||||
binding.capturedImageView.setImageBitmap(BitmapFactory.decodeFile(rotatedImageFile?.absolutePath))
|
||||
binding.retakeLL.setOnClickListener {
|
||||
finish() // Go back to the camera activity
|
||||
}
|
||||
|
||||
binding.cancelLL.setOnClickListener {
|
||||
deleteImageFile(photoFile!!.absolutePath)
|
||||
finish() // Go back to the camera activity
|
||||
}
|
||||
|
||||
binding.saveLL.setOnClickListener {
|
||||
val intent = Intent()
|
||||
intent.putExtra("filePath", photoUri!!)
|
||||
setResult(RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteImageFile(filePath: String): Boolean {
|
||||
val file = File(filePath)
|
||||
return if (file.exists()) {
|
||||
val isDeleted = file.delete()
|
||||
if (isDeleted) {
|
||||
Log.d("FileDeletion", "File deleted successfully: $filePath")
|
||||
} else {
|
||||
Log.e("FileDeletion", "File deletion failed: $filePath")
|
||||
}
|
||||
isDeleted
|
||||
} else {
|
||||
Log.e("FileDeletion", "File does not exist: $filePath")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure to shut down the executor
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cameraExecutor.shutdown()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.cpm.india.cameraai.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Matrix
|
||||
import android.util.Log
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import com.google.mlkit.vision.face.Face
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
object GetProperImageRotation {
|
||||
fun getRotatedImageFile(photoFile: File?, filePath: String?, context: Context?): File? {
|
||||
val option = BitmapFactory.Options()
|
||||
option.inSampleSize = 4
|
||||
val convertedBitmap: Bitmap =
|
||||
modifyOrientation(
|
||||
BitmapFactory.decodeFile(photoFile!!.absolutePath, option),
|
||||
photoFile.absolutePath, context
|
||||
)
|
||||
return saveImage(convertedBitmap, filePath)
|
||||
|
||||
}
|
||||
|
||||
private fun saveImage(image: Bitmap, filePath: String?): File? {
|
||||
// Get the file object
|
||||
val file = filePath?.let { File(it) }
|
||||
val os = BufferedOutputStream(FileOutputStream(file))
|
||||
image.compress(Bitmap.CompressFormat.JPEG, 100, os)
|
||||
os.close()
|
||||
return file
|
||||
}
|
||||
|
||||
private fun modifyOrientation(
|
||||
bitmap: Bitmap,
|
||||
image_absolute_path: String,
|
||||
context: Context?
|
||||
): Bitmap {
|
||||
val ei = ExifInterface(image_absolute_path)
|
||||
val orientation: Int =
|
||||
ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
|
||||
Log.e("orintation", "" + getScreenOrientation(context))
|
||||
when (orientation) {
|
||||
ExifInterface.ORIENTATION_ROTATE_90 -> {
|
||||
return rotate(bitmap, 90f)
|
||||
}
|
||||
ExifInterface.ORIENTATION_ROTATE_180 -> {
|
||||
return rotate(bitmap, 180f)
|
||||
}
|
||||
|
||||
ExifInterface.ORIENTATION_TRANSVERSE -> {
|
||||
return rotate(bitmap, 270f)
|
||||
}
|
||||
|
||||
ExifInterface.ORIENTATION_ROTATE_270 -> {
|
||||
return rotate(bitmap, 270f)
|
||||
}
|
||||
|
||||
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> {
|
||||
return flip(bitmap, true, vertical = false)
|
||||
}
|
||||
|
||||
ExifInterface.ORIENTATION_FLIP_VERTICAL -> {
|
||||
return flip(bitmap, false, vertical = true)
|
||||
}
|
||||
|
||||
else -> {
|
||||
return bitmap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun rotate(bitmap: Bitmap, degrees: Float): Bitmap {
|
||||
val matrix = Matrix()
|
||||
matrix.postRotate(degrees)
|
||||
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
||||
}
|
||||
|
||||
private fun flip(bitmap: Bitmap, horizontal: Boolean, vertical: Boolean): Bitmap {
|
||||
val matrix = Matrix()
|
||||
matrix.preScale(if (horizontal) (-1f) else 1f, if (vertical) (-1f) else 1f)
|
||||
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true);
|
||||
}
|
||||
|
||||
// Function to check for blink and movement
|
||||
fun checkBlinkAndMovement(face: Face): Boolean {
|
||||
// Step 1: Check blink detection
|
||||
val leftEyeOpenProbability = face.leftEyeOpenProbability ?: -1f
|
||||
val rightEyeOpenProbability = face.rightEyeOpenProbability ?: -1f
|
||||
// If both eyes are open or closed, consider it a blink detection scenario
|
||||
val isBlinking = leftEyeOpenProbability < 0.3 && rightEyeOpenProbability < 0.3
|
||||
// Step 2: Check for facial movement (pose detection)
|
||||
val headEulerAngleX = face.headEulerAngleX // Head tilt up/down
|
||||
val headEulerAngleY = face.headEulerAngleY // Head turn left/right
|
||||
val headEulerAngleZ = face.headEulerAngleZ // Head tilt sideways
|
||||
// If the face poses (angles) change significantly between frames, consider it as movement
|
||||
val isMoving =
|
||||
headEulerAngleX.absoluteValue > 10 || headEulerAngleY.absoluteValue > 10 || headEulerAngleZ.absoluteValue > 10
|
||||
// Return true if either blinking or movement is detected
|
||||
return isBlinking && isMoving
|
||||
}
|
||||
|
||||
fun getScreenOrientation(context: Context?): Int {
|
||||
val configuration = context?.resources!!.configuration
|
||||
// If the configuration returns ORIENTATION_UNDEFINED, manually check width/height
|
||||
if (configuration.orientation == Configuration.ORIENTATION_UNDEFINED) {
|
||||
val displayMetrics = context.resources!!.displayMetrics
|
||||
return if (displayMetrics.widthPixels > displayMetrics.heightPixels) {
|
||||
Configuration.ORIENTATION_LANDSCAPE
|
||||
} else {
|
||||
Configuration.ORIENTATION_PORTRAIT
|
||||
}
|
||||
}
|
||||
|
||||
return configuration.orientation
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package com.cpm.india.cameraai.utils
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Rect
|
||||
import android.media.Image
|
||||
import android.util.Log
|
||||
import com.google.mlkit.vision.face.Face
|
||||
import org.opencv.android.Utils
|
||||
import org.opencv.core.Core
|
||||
import org.opencv.core.Mat
|
||||
import org.opencv.core.MatOfDouble
|
||||
import org.opencv.imgproc.Imgproc
|
||||
import java.nio.ReadOnlyBufferException
|
||||
import kotlin.experimental.inv
|
||||
|
||||
object Utils {
|
||||
var eyeBlink = false
|
||||
private fun rotateBitmap(bitmap: Bitmap, rotationDegrees: Int, flipX: Boolean): Bitmap {
|
||||
val matrix = Matrix()
|
||||
// Rotate the image back to straight.
|
||||
matrix.postRotate(rotationDegrees.toFloat())
|
||||
// Mirror the image along the X or Y axis.
|
||||
matrix.postScale(if (flipX) -1.0f else 1.0f, 1.0f)
|
||||
val rotatedBitmap =
|
||||
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
||||
// Recycle the old bitmap if it has changed.
|
||||
if (rotatedBitmap != bitmap) {
|
||||
bitmap.recycle()
|
||||
}
|
||||
return rotatedBitmap
|
||||
}
|
||||
private fun YUV_420_888toNV21(image: Image): ByteArray {
|
||||
val width = image.width
|
||||
val height = image.height
|
||||
val ySize = width * height
|
||||
val uvSize = width * height / 4
|
||||
val nv21 = ByteArray(ySize + uvSize * 2)
|
||||
val yBuffer = image.planes[0].buffer // Y
|
||||
val uBuffer = image.planes[1].buffer // U
|
||||
val vBuffer = image.planes[2].buffer // V
|
||||
var rowStride = image.planes[0].rowStride
|
||||
assert(image.planes[0].pixelStride == 1)
|
||||
|
||||
var pos = 0
|
||||
|
||||
if (rowStride == width) { // likely
|
||||
yBuffer[nv21, 0, ySize]
|
||||
pos += ySize
|
||||
} else {
|
||||
var yBufferPos = -rowStride.toLong() // not an actual position
|
||||
while (pos < ySize) {
|
||||
yBufferPos += rowStride.toLong()
|
||||
yBuffer.position(yBufferPos.toInt())
|
||||
yBuffer[nv21, pos, width]
|
||||
pos += width
|
||||
}
|
||||
}
|
||||
|
||||
rowStride = image.planes[2].rowStride
|
||||
val pixelStride = image.planes[2].pixelStride
|
||||
assert(rowStride == image.planes[1].rowStride)
|
||||
assert(pixelStride == image.planes[1].pixelStride)
|
||||
if (pixelStride == 2 && rowStride == width && uBuffer[0] == vBuffer[1]) {
|
||||
// maybe V an U planes overlap as per NV21, which means vBuffer[1] is alias of uBuffer[0]
|
||||
val savePixel = vBuffer[1]
|
||||
try {
|
||||
vBuffer.put(1, savePixel.inv() as Byte)
|
||||
if (uBuffer[0] == savePixel.inv() as Byte) {
|
||||
vBuffer.put(1, savePixel)
|
||||
vBuffer.position(0)
|
||||
uBuffer.position(0)
|
||||
vBuffer[nv21, ySize, 1]
|
||||
uBuffer[nv21, ySize + 1, uBuffer.remaining()]
|
||||
|
||||
return nv21 // shortcut
|
||||
}
|
||||
} catch (ex: ReadOnlyBufferException) {
|
||||
// unfortunately, we cannot check if vBuffer and uBuffer overlap
|
||||
}
|
||||
// unfortunately, the check failed. We must save U and V pixel by pixel
|
||||
vBuffer.put(1, savePixel)
|
||||
}
|
||||
|
||||
// other optimizations could check if (pixelStride == 1) or (pixelStride == 2),
|
||||
// but performance gain would be less significant
|
||||
for (row in 0 until height / 2) {
|
||||
for (col in 0 until width / 2) {
|
||||
val vuPos = col * pixelStride + row * rowStride
|
||||
nv21[pos++] = vBuffer[vuPos]
|
||||
nv21[pos++] = uBuffer[vuPos]
|
||||
}
|
||||
}
|
||||
|
||||
return nv21
|
||||
}
|
||||
private fun getResizedBitmap(bm: Bitmap): Bitmap {
|
||||
val width = bm.width
|
||||
val height = bm.height
|
||||
val scaleWidth = (112f) / width
|
||||
val scaleHeight = (112f) / height
|
||||
// CREATE A MATRIX FOR THE MANIPULATION
|
||||
val matrix = Matrix()
|
||||
// RESIZE THE BIT MAP
|
||||
matrix.postScale(scaleWidth, scaleHeight)
|
||||
// "RECREATE" THE NEW BITMAP
|
||||
val resizedBitmap = Bitmap.createBitmap(
|
||||
bm, 0, 0, width, height, matrix, false
|
||||
)
|
||||
bm.recycle()
|
||||
return resizedBitmap
|
||||
}
|
||||
fun isValidHumanFace(face: Face): Boolean {
|
||||
val boundingBox = face.boundingBox
|
||||
val faceWidth = boundingBox.width()
|
||||
val faceHeight = boundingBox.height()
|
||||
// Check face size or proportions (e.g., width to height ratio)
|
||||
val aspectRatio = faceWidth.toFloat() / faceHeight.toFloat()
|
||||
if (aspectRatio < 0.75 || aspectRatio > 1.3) {
|
||||
return false // Unusual face shape, likely not a human face
|
||||
}
|
||||
// Additional checks can be added here
|
||||
return true
|
||||
}
|
||||
|
||||
// Function to check if the face is too far based on bounding box size
|
||||
fun isFaceTooFar(boundingBox: Rect?): Boolean {
|
||||
// Use width or height of the bounding box to determine if the face is too far
|
||||
val faceWidth = boundingBox?.width()
|
||||
// Set a threshold for face width (this value will depend on your camera and testing)
|
||||
val farThreshold = 100 // Example threshold, adjust based on testing
|
||||
// If the face width is smaller than the threshold, consider it too far
|
||||
return faceWidth!! < farThreshold
|
||||
}
|
||||
|
||||
fun checkBlurriness(result: Bitmap): Int {
|
||||
val destination = Mat()
|
||||
val image = Mat()
|
||||
val matGray = Mat()
|
||||
val std = MatOfDouble()
|
||||
val median = MatOfDouble()
|
||||
Utils.bitmapToMat(result, image)
|
||||
Imgproc.cvtColor(image, matGray, Imgproc.COLOR_BGR2GRAY)
|
||||
Imgproc.Laplacian(matGray, destination, 3)
|
||||
Core.meanStdDev(destination, median, std)
|
||||
val variance = Math.pow(std.get(0, 0)[0], 2.0)
|
||||
Log.i("Variance : ", variance.toString())
|
||||
return if (variance < 10) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
fun calculateBrightness(bitmap: Bitmap): Float {
|
||||
return calculateBrightnessEstimate(bitmap, 1)
|
||||
}
|
||||
private fun calculateBrightnessEstimate(bitmap: Bitmap, pixelSpacing: Int): Float {
|
||||
var r = 0
|
||||
var g = 0
|
||||
var b = 0
|
||||
val height = bitmap.height
|
||||
val width = bitmap.width
|
||||
var n = 0
|
||||
var lux = 0.0f
|
||||
val pixels = IntArray(width * height)
|
||||
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
var i = 0
|
||||
while (i < pixels.size) {
|
||||
val color = pixels[i]
|
||||
r = Color.red(color)
|
||||
g = Color.green(color)
|
||||
b = Color.blue(color)
|
||||
val luminance = (r * 0.2126f + g * 0.7152f + b * 0.0722f) / 255
|
||||
lux += luminance
|
||||
n++
|
||||
i += pixelSpacing
|
||||
}
|
||||
return lux / n
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512"
|
||||
android:width="512dp"
|
||||
android:height="512dp">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0 0l512 0 0 512 -512 0z" />
|
||||
<path
|
||||
android:pathData="M512 256A256 256 0 0 1 256 512 256 256 0 0 1 0 256 256 256 0 0 1 256 0 256 256 0 0 1 512 256Z"
|
||||
android:fillColor="#ffffff" />
|
||||
<path
|
||||
android:pathData="M511.5 256A255.5 255.5 0 0 1 256 511.5 255.5 255.5 0 0 1 0.5 256 255.5 255.5 0 0 1 256 0.5 255.5 255.5 0 0 1 511.5 256Z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#707070" />
|
||||
<group
|
||||
android:translateX="45"
|
||||
android:translateY="45">
|
||||
<path
|
||||
android:pathData="M422 211A211 211 0 0 1 211 422 211 211 0 0 1 0 211 211 211 0 0 1 211 0 211 211 0 0 1 422 211Z"
|
||||
android:fillColor="#d2d9e0" />
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,16 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="@color/white">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9,12c0,1.66 1.34,3 3,3s3,-1.34 3,-3s-1.34,-3 -3,-3S9,10.34 9,12zM13,12c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1s0.45,-1 1,-1S13,11.45 13,12z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8,10V8H5.09C6.47,5.61 9.05,4 12,4c3.72,0 6.85,2.56 7.74,6h2.06c-0.93,-4.56 -4.96,-8 -9.8,-8C8.73,2 5.82,3.58 4,6.01V4H2v6H8z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16,14v2h2.91c-1.38,2.39 -3.96,4 -6.91,4c-3.72,0 -6.85,-2.56 -7.74,-6H2.2c0.93,4.56 4.96,8 9.8,8c3.27,0 6.18,-1.58 8,-4.01V20h2v-6H16z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" >
|
||||
<corners android:radius="75dp" />
|
||||
<solid
|
||||
android:color="#99D2D9E0" />
|
||||
<stroke
|
||||
android:color="#99D2D9E0"
|
||||
android:width="2dp"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke android:color="#FFF" android:width="1dp"/>
|
||||
<corners android:radius="100dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,149 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".camera.CameraActivity">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="130dp"
|
||||
android:layout_marginBottom="124dp"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toTopOf="@+id/infoPanel"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintVertical_bias="1.0" />
|
||||
|
||||
<androidx.camera.view.PreviewView
|
||||
android:id="@+id/texture"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0">
|
||||
</androidx.camera.view.PreviewView>
|
||||
|
||||
<Space
|
||||
android:id="@+id/space"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="1dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/capturebtn_camera_activity"
|
||||
app:layout_constraintEnd_toStartOf="@+id/capturebtn_camera_activity"
|
||||
app:layout_constraintTop_toTopOf="@+id/capturebtn_camera_activity" />
|
||||
|
||||
<View
|
||||
android:id="@+id/transparent_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="#88676767"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/infoPanel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="70dp"
|
||||
android:layout_marginEnd="70dp"
|
||||
android:layout_marginBottom="240dp"
|
||||
android:background="@drawable/rounded_corners"
|
||||
android:padding="8dp"
|
||||
android:visibility="invisible"
|
||||
android:text="@string/need_all_green_dots_to_start_taking_pictures"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#80000000"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivFlash"
|
||||
android:layout_width="38dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_marginStart="50dp"
|
||||
android:layout_marginBottom="100dp"
|
||||
android:background="@drawable/roundedcorner"
|
||||
android:src="@mipmap/ic_flash_off_white_24dp"
|
||||
android:textAlignment="center"
|
||||
android:scaleType="centerInside"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/switch_camera"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/capturebtn_camera_activity"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginStart="128dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="80dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/camera_capture_button"
|
||||
android:scaleType="centerInside"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/switch_camera"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:srcCompat="@drawable/ic_camera_icon" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout_alpha"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="25dp"
|
||||
android:layout_marginEnd="25dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toTopOf="@+id/infoPanel"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvImageBlurr"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="387dp"
|
||||
android:text="@string/no_face_detected"
|
||||
android:textColor="@color/red"
|
||||
android:textSize="25sp"
|
||||
android:gravity="center"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/texture" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/switch_camera"
|
||||
android:layout_width="38dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_marginBottom="100dp"
|
||||
android:layout_marginEnd="50dp"
|
||||
android:background="@drawable/roundedcorner"
|
||||
android:contentDescription="@string/switch_camera"
|
||||
android:src="@drawable/outline_flip_camera_android_24"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,103 @@
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:background="@color/black"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/capturedImageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="?actionBarSize"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:background="@color/black"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:orientation="horizontal"
|
||||
android:background="@android:color/holo_blue_light"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:weightSum="3">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/cancelLL"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:layout_weight="1"
|
||||
>
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@mipmap/close"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/cancelTV"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-condensed-medium"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:textSize="16sp"
|
||||
android:text="Cancel"
|
||||
android:textColor="@color/black"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/retakeLL"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:layout_weight="1"
|
||||
>
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@mipmap/retake"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/retakeTV"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:fontFamily="sans-serif-condensed-medium"
|
||||
android:textSize="16sp"
|
||||
android:text="Retake"
|
||||
android:textColor="@color/black"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/saveLL"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:layout_weight="1"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@mipmap/tick"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/doneTV"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:fontFamily="sans-serif-condensed-medium"
|
||||
android:textSize="16sp"
|
||||
android:text="Ok"
|
||||
android:textColor="@color/black" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 306 B |
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 223 B |
|
After Width: | Height: | Size: 158 B |
|
After Width: | Height: | Size: 345 B |
|
After Width: | Height: | Size: 239 B |
|
After Width: | Height: | Size: 485 B |
|
After Width: | Height: | Size: 318 B |
|
After Width: | Height: | Size: 602 B |
|
After Width: | Height: | Size: 386 B |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="red">#FF0000</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<resources>
|
||||
<string name="switch_camera">switch camera</string>
|
||||
<string name="face_detected">Face Detected</string>
|
||||
<string name="no_face_detected">No Face Detected!</string>
|
||||
<string name="camera_capture_button">Camera Capture Button</string>
|
||||
<string name="need_all_green_dots_to_start_taking_pictures">Need all green dots to start taking pictures</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.cpm.india.cameraai
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||