From 76992c0d810c776a15d3fc858bc22ef302a302f0 Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Mon, 22 Apr 2024 00:26:44 -0400 Subject: [PATCH 1/6] feat: v4 --- app/build.gradle | 10 +- app/src/main/AndroidManifest.xml | 1 - .../triniwiz/videorecorder/MainActivity.kt | 38 +- .../io/github/triniwiz/videorecorder/Photo.kt | 9 +- barcodeScanning/build.gradle | 17 +- barcodeScanning/gradle.properties | 25 +- barcodeScanning/publish.gradle | 58 -- barcodeScanning/src/main/AndroidManifest.xml | 3 +- .../barcodescanning/BarcodeScanner.kt | 113 +- .../fancycamera/barcodescanning/Result.kt | 21 +- build.gradle | 37 +- digitalInkRecognition/build.gradle | 9 +- .../src/main/AndroidManifest.xml | 3 +- facedetection/build.gradle | 16 +- facedetection/gradle.properties | 27 +- facedetection/publish.gradle | 59 -- facedetection/src/main/AndroidManifest.xml | 3 +- .../facedetection/FaceDetection.kt | 59 +- .../fancycamera/facedetection/Result.kt | 11 +- fancycamera/build.gradle | 15 +- fancycamera/gradle.properties | 24 +- fancycamera/publish.gradle | 58 -- fancycamera/src/main/AndroidManifest.xml | 3 +- .../io/github/triniwiz/fancycamera/Camera.kt | 586 ++++------- .../io/github/triniwiz/fancycamera/Camera2.kt | 973 +++++++++--------- .../github/triniwiz/fancycamera/CameraBase.kt | 185 +--- .../github/triniwiz/fancycamera/CameraLens.kt | 14 + .../triniwiz/fancycamera/CameraPosition.kt | 13 + .../triniwiz/fancycamera/FancyCamera.kt | 120 +-- .../triniwiz/fancycamera/ImageProcessor.kt | 35 + .../java/io/github/triniwiz/fancycamera/ML.kt | 625 ++--------- gradle.properties | 38 +- gradle/wrapper/gradle-wrapper.properties | 2 +- imageLabeling/build.gradle | 16 +- imageLabeling/gradle.properties | 25 +- imageLabeling/publish.gradle | 59 -- imageLabeling/src/main/AndroidManifest.xml | 3 +- .../imagelabeling/ImageLabeling.kt | 60 +- objectDetection/build.gradle | 16 +- objectDetection/gradle.properties | 25 +- objectDetection/publish.gradle | 58 -- objectDetection/src/main/AndroidManifest.xml | 3 +- .../objectdetection/ObjectDetection.kt | 62 +- .../fancycamera/objectdetection/Result.kt | 11 +- poseDetection/build.gradle | 17 +- poseDetection/gradle.properties | 25 +- poseDetection/publish.gradle | 58 -- poseDetection/src/main/AndroidManifest.xml | 3 +- .../posedetection/PoseDetection.kt | 74 +- scripts/upload.gradle | 214 ++++ selfieSegmentation/build.gradle | 19 +- selfieSegmentation/gradle.properties | 25 +- selfieSegmentation/publish.gradle | 61 -- .../src/main/AndroidManifest.xml | 3 +- .../selfiesegmentation/SelfieSegmentation.kt | 58 +- textRecognition/build.gradle | 17 +- textRecognition/gradle.properties | 24 +- textRecognition/publish.gradle | 61 -- textRecognition/src/main/AndroidManifest.xml | 3 +- .../fancycamera/textrecognition/Result.kt | 11 +- .../textrecognition/TextRecognition.kt | 65 +- 61 files changed, 1609 insertions(+), 2677 deletions(-) delete mode 100644 barcodeScanning/publish.gradle delete mode 100644 facedetection/publish.gradle delete mode 100644 fancycamera/publish.gradle create mode 100644 fancycamera/src/main/java/io/github/triniwiz/fancycamera/CameraLens.kt create mode 100644 fancycamera/src/main/java/io/github/triniwiz/fancycamera/ImageProcessor.kt delete mode 100644 imageLabeling/publish.gradle delete mode 100644 objectDetection/publish.gradle delete mode 100644 poseDetection/publish.gradle create mode 100644 scripts/upload.gradle delete mode 100644 selfieSegmentation/publish.gradle delete mode 100644 textRecognition/publish.gradle diff --git a/app/build.gradle b/app/build.gradle index 89837ac..35d278a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion COMPILE_SDK_VERSION as int + compileSdk = COMPILE_SDK_VERSION as int buildToolsVersion BUILD_TOOLS_VERSION as String defaultConfig { applicationId "co.fitcom.videorecorder" @@ -20,9 +20,10 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } + namespace 'io.github.triniwiz.videorecorder' } dependencies { @@ -41,8 +42,7 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' implementation "androidx.core:core-ktx:1.9.0" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "androidx.exifinterface:exifinterface:1.3.6" + implementation "androidx.exifinterface:exifinterface:1.3.7" } repositories { mavenCentral() diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2b2a1d7..2e994f4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ diff --git a/app/src/main/java/io/github/triniwiz/videorecorder/MainActivity.kt b/app/src/main/java/io/github/triniwiz/videorecorder/MainActivity.kt index a18daff..ce7c875 100644 --- a/app/src/main/java/io/github/triniwiz/videorecorder/MainActivity.kt +++ b/app/src/main/java/io/github/triniwiz/videorecorder/MainActivity.kt @@ -13,8 +13,8 @@ import android.widget.RelativeLayout import android.widget.TextView import android.widget.VideoView import io.github.triniwiz.fancycamera.* -import org.json.JSONArray -import org.json.JSONObject +import io.github.triniwiz.fancycamera.barcodescanning.BarcodeScanner +import io.github.triniwiz.fancycamera.textrecognition.TextRecognition import java.io.File import java.io.FileOutputStream import java.io.IOException @@ -23,6 +23,8 @@ import java.util.* import java.util.concurrent.ExecutorService import java.util.concurrent.Executors +import kotlin.collections.ArrayList +import kotlin.math.pow class MainActivity : AppCompatActivity() { internal lateinit var container: RelativeLayout @@ -37,15 +39,16 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // FancyCamera.forceV1 = true +// FancyCamera.forceV1 = true setContentView(R.layout.activity_main) videoPlayer = findViewById(R.id.videoPlayer) durationView = findViewById(R.id.durationView) container = findViewById(R.id.container) cameraView = FancyCamera(this) - // cameraView.ratio = "16:9" + cameraView.ratio = "16:9" cameraView.autoFocus = true cameraView.position = CameraPosition.BACK + cameraView.processEveryNthFrame = 9 cameraView.setListener(object : CameraEventListenerUI() { override fun onReadyUI() { } @@ -100,7 +103,14 @@ class MainActivity : AppCompatActivity() { } }) - cameraView.detectorType = DetectorType.All // disable to use recorder + + cameraView.addImageProcessor( + BarcodeScanner() + ) + cameraView.addImageProcessor( + TextRecognition() + ) + cameraView.setOnBarcodeScanningListener(object : ImageAnalysisCallback { override fun onSuccess(result: Any) { println("setOnBarcodeScanningListener: Success $result") @@ -173,7 +183,7 @@ class MainActivity : AppCompatActivity() { }) cameraView.saveToGallery = true container.addView(cameraView) - // processBarcodeBitmap() + // processBarcodeBitmap() } @@ -197,16 +207,11 @@ class MainActivity : AppCompatActivity() { } val bm = BitmapFactory.decodeFile(barcodeFile.absolutePath) - val json = JSONObject() - json.put("detectorType", 0) - val barcode = JSONObject() - val formats = JSONArray() - formats.put(0) - barcode.put("barcodeFormat", formats) - json.put("barcodeScanning", barcode) - ML.processImage(bm, 0, json.toString(), object : ImageAnalysisCallback { + val processors = ArrayList>(1) + processors.add(BarcodeScanner() as ImageProcessor) + ML.processImage(bm, 0, processors, object : ImageAnalysisCallback { override fun onSuccess(result: Any) { - (result as? List>)?.let { values -> + (result as? List>)?.let { values -> for (value in values) { val type = value[0] val data = value[1] @@ -304,8 +309,7 @@ class MainActivity : AppCompatActivity() { levelsTask!!.scheduleAtFixedRate(object : TimerTask() { override fun run() { runOnUiThread { - level = Math.pow(10.0, 0.02 * cameraView.db) - Log.d("co.test", "Audio Levels$level") + level = 10.0.pow(0.02 * cameraView.db) } } }, 0, 1000) diff --git a/app/src/main/java/io/github/triniwiz/videorecorder/Photo.kt b/app/src/main/java/io/github/triniwiz/videorecorder/Photo.kt index 0c98bd3..6a557cd 100644 --- a/app/src/main/java/io/github/triniwiz/videorecorder/Photo.kt +++ b/app/src/main/java/io/github/triniwiz/videorecorder/Photo.kt @@ -22,8 +22,10 @@ class Photo : AppCompatActivity() { imageView = findViewById(R.id.imageView) cameraView = findViewById(R.id.PhotoView) //FancyCamera.forceV1 = true + cameraView.defaultLens = CameraLens.telephoto cameraView.autoFocus = true - // cameraView.quality = Quality.HIGHEST + cameraView.saveToGallery = true + cameraView.quality = Quality.HIGHEST cameraView.setListener(object : CameraEventListenerUI() { override fun onReadyUI() { @@ -58,7 +60,7 @@ class Photo : AppCompatActivity() { } }) - cameraView.detectorType = DetectorType.None + cameraView.setOnFacesDetectedListener(object : ImageAnalysisCallback { override fun onSuccess(result: Any) { println("setOnFacesDetectedListener: Success ${result}") @@ -80,7 +82,8 @@ class Photo : AppCompatActivity() { } }) // cameraView.saveToGallery = true - cameraView.autoSquareCrop = true + // cameraView.autoSquareCrop = true + cameraView.defaultLens = CameraLens.auto } fun takePhoto(view: View) { diff --git a/barcodeScanning/build.gradle b/barcodeScanning/build.gradle index 7f381fe..e9b9ed2 100644 --- a/barcodeScanning/build.gradle +++ b/barcodeScanning/build.gradle @@ -4,13 +4,15 @@ plugins { } android { - compileSdkVersion COMPILE_SDK_VERSION as int + compileSdk = COMPILE_SDK_VERSION as int buildToolsVersion BUILD_TOOLS_VERSION as String defaultConfig { minSdkVersion 17 targetSdkVersion 33 + versionName = VERSION_NAME as String + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } @@ -22,22 +24,23 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } + namespace 'io.github.triniwiz.fancycamera.barcodescanning' } dependencies { + implementation project(':fancycamera') implementation "com.google.code.gson:gson:$gson_version" - implementation 'com.google.mlkit:barcode-scanning:17.0.3' - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'com.google.mlkit:barcode-scanning:17.2.0' implementation "androidx.appcompat:appcompat:$app_compat_version" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } -apply from: 'publish.gradle' +apply from: "${rootProject.projectDir}/scripts/upload.gradle" diff --git a/barcodeScanning/gradle.properties b/barcodeScanning/gradle.properties index 05e395c..ccc7631 100644 --- a/barcodeScanning/gradle.properties +++ b/barcodeScanning/gradle.properties @@ -1,23 +1,8 @@ -GROUP=io.github.triniwiz.fancycamera - -POM_ARTIFACT_ID=barcodeScanning -VERSION_NAME=1.0.2 - -POM_NAME=barcodeScanning +POM_ARTIFACT_ID=barcode-scanning +POM_NAME=MLKit Barcode Scanning POM_PACKAGING=aar - POM_DESCRIPTION=Fancy Android Camera BarcodeScanning -POM_INCEPTION_YEAR=2019 - -POM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git -POM_SCM_DEV_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git - -POM_LICENCE_NAME=The Apache Software License, Version 2.0 -POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt -POM_LICENCE_DIST=repo -POM_DEVELOPER_ID=triniwiz -POM_DEVELOPER_NAME=Osei Fortune -POM_DEVELOPER_URL=https://github.com/triniwiz \ No newline at end of file +# Prefix and postfix for source and javadoc jars. +JAR_PREFIX=fancycamera- +JAR_POSTFIX= diff --git a/barcodeScanning/publish.gradle b/barcodeScanning/publish.gradle deleted file mode 100644 index c10093a..0000000 --- a/barcodeScanning/publish.gradle +++ /dev/null @@ -1,58 +0,0 @@ -apply plugin: 'signing' -apply plugin: 'maven-publish' -apply plugin: "com.vanniktech.maven.publish" - -allprojects { - plugins.withId("com.vanniktech.maven.publish") { - mavenPublish { - sonatypeHost = "S01" - } - } - - plugins.withId("com.vanniktech.maven.base") { - group = "io.github.triniwiz.fancycamera" - archivesBaseName = "barcodeScanning" - version = "1.0.2" - - mavenPublishing { - publishToMavenCentral("DEFAULT") - - // Will only apply to non snapshot builds. - // Uses credentials as described above, supports both regular and in memory signing. - signAllPublications() - - pom { - name = "FancyCameraBarcodeScanning" - packaging = 'aar' - description = "Fancy Android Camera BarcodeScanning" - inceptionYear = "2019" - url = 'https://github.com/triniwiz/fancycamera' - - licenses { - license { - name = "The Apache License, Version 2.0" - url = "http://www.apache.org/licenses/LICENSE-2.0.txt" - distribution = "http://www.apache.org/licenses/LICENSE-2.0.txt" - } - } - - developers { - developer { - id = 'triniwiz' - name = 'Osei Fortune' - email = 'fortune.osei@yahoo.com' - url = "https://github.com/triniwiz/" - } - } - - scm { - connection 'scm:git@github.com:triniwiz/fancycamera.git' - developerConnection 'scm:git@github.com:triniwiz/fancycamera.git' - url 'https://github.com/triniwiz/fancycamera' - } - } - - pomFromGradleProperties() - } - } -} \ No newline at end of file diff --git a/barcodeScanning/src/main/AndroidManifest.xml b/barcodeScanning/src/main/AndroidManifest.xml index 8fb5928..a5918e6 100644 --- a/barcodeScanning/src/main/AndroidManifest.xml +++ b/barcodeScanning/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + \ No newline at end of file diff --git a/barcodeScanning/src/main/java/io/github/triniwiz/fancycamera/barcodescanning/BarcodeScanner.kt b/barcodeScanning/src/main/java/io/github/triniwiz/fancycamera/barcodescanning/BarcodeScanner.kt index f641981..4f9ab34 100644 --- a/barcodeScanning/src/main/java/io/github/triniwiz/fancycamera/barcodescanning/BarcodeScanner.kt +++ b/barcodeScanning/src/main/java/io/github/triniwiz/fancycamera/barcodescanning/BarcodeScanner.kt @@ -1,22 +1,25 @@ package io.github.triniwiz.fancycamera.barcodescanning import android.graphics.Bitmap -import com.google.android.gms.tasks.Task -import com.google.android.gms.tasks.TaskCompletionSource +import android.media.Image +import com.google.android.gms.tasks.Tasks import com.google.gson.Gson +import com.google.gson.annotations.SerializedName import com.google.mlkit.vision.barcode.BarcodeScannerOptions import com.google.mlkit.vision.barcode.common.Barcode import com.google.mlkit.vision.common.InputImage +import io.github.triniwiz.fancycamera.ImageProcessor import org.json.JSONObject -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors +import java.util.concurrent.ExecutionException +import java.util.concurrent.FutureTask -class BarcodeScanner { - private var executor: ExecutorService = Executors.newSingleThreadExecutor() - fun processImage(image: InputImage, options: Options): Task { - val task = TaskCompletionSource() +class BarcodeScanner : ImageProcessor { + var options = Options() + override val type: Int = 0 + override fun process(image: InputImage): FutureTask { val opts = BarcodeScannerOptions.Builder() + if (options.barcodeFormat.isEmpty()) { opts.setBarcodeFormats(BarcodeFormat.ALL.format) } else { @@ -33,60 +36,95 @@ class BarcodeScanner { ) } } + val client = com.google.mlkit.vision.barcode.BarcodeScanning.getClient(opts.build()) val gson = Gson() - client.process(image) - .addOnSuccessListener(executor) { + + return FutureTask { + try { + val results = Tasks.await(client.process(image)) val result = mutableListOf() - for (barcode in it) { + for (barcode in results) { result.add(Result(barcode)) } - val json = if (result.isNotEmpty()) { + if (result.isNotEmpty()) { gson.toJson(result) } else { "" } - + } catch (e: ExecutionException) { + throw e + } catch (e: InterruptedException) { + throw e + } finally { client.close() - task.setResult(json) } - .addOnFailureListener(executor, { - task.setException(it) - }) - return task.task + } + + } + + override fun process(image: Image, rotation: Int): FutureTask { + return process(InputImage.fromMediaImage(image, rotation)) } - fun processBytes( + override fun process(bitmap: Bitmap, rotation: Int): FutureTask { + val input = InputImage.fromBitmap(bitmap, rotation) + return process(input) + } + + override fun process( bytes: ByteArray, width: Int, height: Int, rotation: Int, - format: Int, - options: Options - ): Task { + format: Int + ): FutureTask { val input = InputImage.fromByteArray(bytes, width, height, rotation, format) - return processImage(input, options) + return process(input) } - fun processBitmap(bitmap: Bitmap, rotation: Int, options: Options): Task { - val input = InputImage.fromBitmap(bitmap, rotation) - return processImage(input, options) - } enum class BarcodeFormat(internal val format: Int) { + @SerializedName("all") ALL(Barcode.FORMAT_ALL_FORMATS), + + @SerializedName("code_128") CODE_128(Barcode.FORMAT_CODE_128), + + @SerializedName("code_39") CODE_39(Barcode.FORMAT_CODE_39), + + @SerializedName("code_93") CODE_93(Barcode.FORMAT_CODE_93), + + @SerializedName("codabar") CODABAR(Barcode.FORMAT_CODABAR), + + @SerializedName("data_matrix") DATA_MATRIX(Barcode.FORMAT_DATA_MATRIX), + + @SerializedName("ean_13") EAN_13(Barcode.FORMAT_EAN_13), + + @SerializedName("ean_8") EAN_8(Barcode.FORMAT_EAN_8), + + @SerializedName("itf") ITF(Barcode.FORMAT_ITF), + + @SerializedName("qr_code") QR_CODE(Barcode.FORMAT_QR_CODE), + + @SerializedName("upc_a") UPC_A(Barcode.FORMAT_UPC_A), + + @SerializedName("upc_e") UPC_E(Barcode.FORMAT_UPC_E), + + @SerializedName("pdf417") PDF417(Barcode.FORMAT_PDF417), + + @SerializedName("aztec") AZTEC(Barcode.FORMAT_AZTEC); companion object { @@ -102,6 +140,26 @@ class BarcodeScanner { } } + val value: String + get() { + return when (this) { + ALL -> "all" + CODE_128 -> "code_128" + CODE_39 -> "code_39" + CODE_93 -> "code_93" + CODABAR -> "codabar" + DATA_MATRIX -> "data_matrix" + EAN_13 -> "ean_13" + EAN_8 -> "ean_8" + ITF -> "itf" + QR_CODE -> "qr_code" + UPC_A -> "upc_a" + UPC_E -> "upc_e" + PDF417 -> "pdf417" + AZTEC -> "aztec" + } + } + } class Options { @@ -155,4 +213,5 @@ class BarcodeScanner { } } } + } \ No newline at end of file diff --git a/barcodeScanning/src/main/java/io/github/triniwiz/fancycamera/barcodescanning/Result.kt b/barcodeScanning/src/main/java/io/github/triniwiz/fancycamera/barcodescanning/Result.kt index 67dac0f..d877bf5 100644 --- a/barcodeScanning/src/main/java/io/github/triniwiz/fancycamera/barcodescanning/Result.kt +++ b/barcodeScanning/src/main/java/io/github/triniwiz/fancycamera/barcodescanning/Result.kt @@ -119,8 +119,10 @@ class Result(barcode: Barcode) { } - class CalenderEvent(event: - Barcode.CalendarEvent) { + class CalenderEvent( + event: + Barcode.CalendarEvent + ) { val description = event.description val location = event.location val organizer = event.organizer @@ -158,7 +160,7 @@ class Result(barcode: Barcode) { val addressList = mutableListOf
() for (address in info.addresses) { addressList.add( - Address(address) + Address(address) ) } addresses = addressList.toTypedArray() @@ -187,13 +189,14 @@ class Result(barcode: Barcode) { } class Bounds(rect: Rect) { - class Origin(val x: Int, val y: Int) - class Size(val width: Int, - val height: Int) - val origin = Origin(rect.left, rect.top) - val size = Size(rect.width(), rect.height()) + val x: Int = rect.left + val y: Int = rect.top + + val width: Int = rect.width() + + val height: Int = rect.height() } class Point(point: android.graphics.Point) { @@ -259,6 +262,6 @@ class Result(barcode: Barcode) { } this.points = points.toTypedArray() format = BarcodeScanner.BarcodeFormat.fromBarcode(barcode.format) - ?: BarcodeScanner.BarcodeFormat.ALL + ?: BarcodeScanner.BarcodeFormat.ALL } } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9d96860..e0b17bc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.8.22' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.1' + classpath 'com.android.tools.build:gradle:8.3.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.vanniktech:gradle-maven-publish-plugin:0.18.0' classpath 'org.jetbrains.dokka:dokka-gradle-plugin:1.4.10.2' @@ -32,3 +32,36 @@ allprojects { task clean(type: Delete) { delete rootProject.buildDir } + + +afterEvaluate { + if (project.hasProperty("android") + && project.name != 'pmd' ) { + android { + lint { + warningsAsErrors true + quiet true + // Caught by the violations plugin. + abortOnError false + } + + // We don't need a BuildConfig constants class. + buildFeatures { + buildConfig = false + } + + // Signing isn't relevant to a library. Our signing is done via + // a GPG key at upload time, there's no APK to sign. + buildTypes { + release { + signingConfig signingConfigs.debug + } + } + } + } + + if (project.tasks.findByName('check')) { + check.dependsOn('checkstyle') + check.finalizedBy violations + } +} \ No newline at end of file diff --git a/digitalInkRecognition/build.gradle b/digitalInkRecognition/build.gradle index 1f42d87..53b9a1d 100644 --- a/digitalInkRecognition/build.gradle +++ b/digitalInkRecognition/build.gradle @@ -21,18 +21,19 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } + namespace 'io.github.triniwiz.fancycamera.digitalInkRecognition' } dependencies { implementation 'com.google.mlkit:digital-ink-recognition:18.1.0' implementation "androidx.appcompat:appcompat:$app_compat_version" - testImplementation 'junit:junit:' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } \ No newline at end of file diff --git a/digitalInkRecognition/src/main/AndroidManifest.xml b/digitalInkRecognition/src/main/AndroidManifest.xml index e62ef32..a5918e6 100644 --- a/digitalInkRecognition/src/main/AndroidManifest.xml +++ b/digitalInkRecognition/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + \ No newline at end of file diff --git a/facedetection/build.gradle b/facedetection/build.gradle index 4a7d9c3..5e52183 100644 --- a/facedetection/build.gradle +++ b/facedetection/build.gradle @@ -4,12 +4,13 @@ plugins { } android { - compileSdkVersion COMPILE_SDK_VERSION as int + compileSdk = COMPILE_SDK_VERSION as int buildToolsVersion BUILD_TOOLS_VERSION as String defaultConfig { minSdkVersion 17 targetSdkVersion 33 + versionName = VERSION_NAME as String testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -22,22 +23,23 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } + namespace 'io.github.triniwiz.fancycamera.facedetection' } dependencies { + implementation project(":fancycamera") implementation "com.google.code.gson:gson:$gson_version" - implementation 'com.google.mlkit:face-detection:16.1.5' - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'com.google.mlkit:face-detection:16.1.6' implementation "androidx.appcompat:appcompat:$app_compat_version" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } -apply from: 'publish.gradle' \ No newline at end of file +apply from: "${rootProject.projectDir}/scripts/upload.gradle" \ No newline at end of file diff --git a/facedetection/gradle.properties b/facedetection/gradle.properties index a97c29d..8654bec 100644 --- a/facedetection/gradle.properties +++ b/facedetection/gradle.properties @@ -1,23 +1,8 @@ -GROUP=io.github.triniwiz.fancycamera - -POM_ARTIFACT_ID=faceDetection -VERSION_NAME=1.0.2 - -POM_NAME=faceDetection +POM_ARTIFACT_ID=face-detection +POM_NAME=MLKit Face Detection POM_PACKAGING=aar +POM_DESCRIPTION=Fancy Android Camera MLKit face detection -POM_DESCRIPTION=Fancy Android Camera FaceDetection -POM_INCEPTION_YEAR=2019 - -POM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git -POM_SCM_DEV_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git - -POM_LICENCE_NAME=The Apache Software License, Version 2.0 -POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt -POM_LICENCE_DIST=repo - -POM_DEVELOPER_ID=triniwiz -POM_DEVELOPER_NAME=Osei Fortune -POM_DEVELOPER_URL=https://github.com/triniwiz \ No newline at end of file +# Prefix and postfix for source and javadoc jars. +JAR_PREFIX=fancycamera- +JAR_POSTFIX= \ No newline at end of file diff --git a/facedetection/publish.gradle b/facedetection/publish.gradle deleted file mode 100644 index 82db533..0000000 --- a/facedetection/publish.gradle +++ /dev/null @@ -1,59 +0,0 @@ -apply plugin: 'signing' -apply plugin: 'maven-publish' -apply plugin: "com.vanniktech.maven.publish" - -allprojects { - plugins.withId("com.vanniktech.maven.publish") { - mavenPublish { - sonatypeHost = "S01" - } - } - - plugins.withId("com.vanniktech.maven.base") { - group = "io.github.triniwiz.fancycamera" - archivesBaseName = "faceDetection" - version = "1.0.2" - - mavenPublishing { - publishToMavenCentral("DEFAULT") - - // Will only apply to non snapshot builds. - // Uses credentials as described above, supports both regular and in memory signing. - signAllPublications() - - pom { - name = "FancyCameraFaceDetection" - packaging = 'aar' - description = "Fancy Android Camera FaceDetection" - inceptionYear = "2019" - url = 'https://github.com/triniwiz/fancycamera' - - licenses { - license { - name = "The Apache License, Version 2.0" - url = "http://www.apache.org/licenses/LICENSE-2.0.txt" - distribution = "http://www.apache.org/licenses/LICENSE-2.0.txt" - } - } - - developers { - developer { - id = 'triniwiz' - name = 'Osei Fortune' - email = 'fortune.osei@yahoo.com' - url = "https://github.com/triniwiz/" - } - } - - scm { - connection 'scm:git@github.com:triniwiz/fancycamera.git' - developerConnection 'scm:git@github.com:triniwiz/fancycamera.git' - url 'https://github.com/triniwiz/fancycamera' - } - } - - pomFromGradleProperties() - } - } -} - diff --git a/facedetection/src/main/AndroidManifest.xml b/facedetection/src/main/AndroidManifest.xml index 441347e..a5918e6 100644 --- a/facedetection/src/main/AndroidManifest.xml +++ b/facedetection/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + \ No newline at end of file diff --git a/facedetection/src/main/java/io/github/triniwiz/fancycamera/facedetection/FaceDetection.kt b/facedetection/src/main/java/io/github/triniwiz/fancycamera/facedetection/FaceDetection.kt index 5942907..720103d 100644 --- a/facedetection/src/main/java/io/github/triniwiz/fancycamera/facedetection/FaceDetection.kt +++ b/facedetection/src/main/java/io/github/triniwiz/fancycamera/facedetection/FaceDetection.kt @@ -1,19 +1,23 @@ package io.github.triniwiz.fancycamera.facedetection import android.graphics.Bitmap -import com.google.android.gms.tasks.* +import com.google.android.gms.tasks.Tasks import com.google.gson.Gson import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.face.FaceDetectorOptions +import io.github.triniwiz.fancycamera.ImageProcessor import org.json.JSONObject -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors +import java.util.concurrent.ExecutionException +import java.util.concurrent.Future +import java.util.concurrent.FutureTask -class FaceDetection() { - private var executor: ExecutorService = Executors.newSingleThreadExecutor() +class FaceDetection : ImageProcessor { + var options = Options() - fun processImage(image: InputImage, options: Options): Task { - val task = TaskCompletionSource() + override val type: Int + get() = 1 + private val gson = Gson() + override fun process(image: InputImage): FutureTask { val opts = FaceDetectorOptions.Builder() if (options.faceTracking) { opts.enableTracking() @@ -24,43 +28,28 @@ class FaceDetection() { opts.setContourMode(options.contourMode.toNative()) opts.setClassificationMode(options.classificationMode.toNative()) val client = com.google.mlkit.vision.face.FaceDetection.getClient(opts.build()) - val gson = Gson() - client.process(image) - .addOnSuccessListener(executor, { + + return FutureTask { + try { + val results = Tasks.await(client.process(image)) val result = mutableListOf() - for (face in it) { + for (face in results) { result.add(Result(face)) } - val json = if (result.isNotEmpty()) { + if (result.isNotEmpty()) { gson.toJson(result) } else { "" } + } catch (e: ExecutionException) { + throw e + } catch (e: InterruptedException) { + throw e + } finally { client.close() - task.setResult(json) - }) - .addOnFailureListener(executor, { - task.setException(it) - }) - return task.task - } - - fun processBytes( - bytes: ByteArray, - width: Int, - height: Int, - rotation: Int, - format: Int, - options: Options - ): Task { - val input = InputImage.fromByteArray(bytes, width, height, rotation, format) - return processImage(input, options) - } - - fun processBitmap(bitmap: Bitmap, rotation: Int, options: Options): Task { - val input = InputImage.fromBitmap(bitmap, rotation) - return processImage(input, options) + } + } } enum class DetectionMode(private val mode: String) { diff --git a/facedetection/src/main/java/io/github/triniwiz/fancycamera/facedetection/Result.kt b/facedetection/src/main/java/io/github/triniwiz/fancycamera/facedetection/Result.kt index 9a0407d..b621911 100644 --- a/facedetection/src/main/java/io/github/triniwiz/fancycamera/facedetection/Result.kt +++ b/facedetection/src/main/java/io/github/triniwiz/fancycamera/facedetection/Result.kt @@ -15,11 +15,12 @@ class Result(instance: Face) { class Bounds(rect: Rect) { - class Origin(val x: Int, val y: Int) - class Size(val width: Int, - val height: Int) + val x: Int = rect.left - val origin = Origin(rect.left, rect.top) - val size = Size(rect.width(), rect.height()) + val y: Int = rect.top + + val width: Int = rect.width() + + val height: Int = rect.height() } } \ No newline at end of file diff --git a/fancycamera/build.gradle b/fancycamera/build.gradle index d504d54..0760357 100644 --- a/fancycamera/build.gradle +++ b/fancycamera/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'kotlin-android' android { compileSdkVersion COMPILE_SDK_VERSION as int buildToolsVersion BUILD_TOOLS_VERSION as String - ndkVersion '21.3.6528147' + ndkVersion '23.2.8568313' defaultConfig { minSdkVersion 17 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -18,16 +18,18 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } + namespace 'io.github.triniwiz.fancycamera' } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "com.google.android.gms:play-services-tasks:18.0.2" api "androidx.appcompat:appcompat:$app_compat_version" + implementation 'com.google.mlkit:vision-common:17.3.0' + implementation 'androidx.databinding:databinding-runtime:8.3.2' //noinspection KtxExtensionAvailable testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.5.2' @@ -38,8 +40,7 @@ dependencies { //api "androidx.camera:camera-extensions:$camerax_ext_version" api "androidx.camera:camera-lifecycle:$camerax_lifecycle_version" api "androidx.camera:camera-video:$camerax_version" - api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - api "androidx.exifinterface:exifinterface:1.3.6" + api "androidx.exifinterface:exifinterface:1.3.7" implementation "com.google.code.gson:gson:$gson_version" } @@ -50,4 +51,4 @@ repositories { } -apply from: 'publish.gradle' \ No newline at end of file +apply from: "${rootProject.projectDir}/scripts/upload.gradle" \ No newline at end of file diff --git a/fancycamera/gradle.properties b/fancycamera/gradle.properties index f854138..ad82f58 100644 --- a/fancycamera/gradle.properties +++ b/fancycamera/gradle.properties @@ -1,23 +1,7 @@ -GROUP=io.github.triniwiz - POM_ARTIFACT_ID=fancycamera -VERSION_NAME=3.0.8 - -POM_NAME=fancycamera +POM_NAME=Fancy Camera POM_PACKAGING=aar -POM_DESCRIPTION=Fancy Android Camera -POM_INCEPTION_YEAR=2019 - -POM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git -POM_SCM_DEV_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git - -POM_LICENCE_NAME=The Apache Software License, Version 2.0 -POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt -POM_LICENCE_DIST=repo - -POM_DEVELOPER_ID=triniwiz -POM_DEVELOPER_NAME=Osei Fortune -POM_DEVELOPER_URL=https://github.com/triniwiz \ No newline at end of file +# Prefix and postfix for source and javadoc jars. +JAR_PREFIX=fancycamera- +JAR_POSTFIX= diff --git a/fancycamera/publish.gradle b/fancycamera/publish.gradle deleted file mode 100644 index e8b2298..0000000 --- a/fancycamera/publish.gradle +++ /dev/null @@ -1,58 +0,0 @@ -apply plugin: 'signing' -apply plugin: 'maven-publish' -apply plugin: "com.vanniktech.maven.publish" - -allprojects { - plugins.withId("com.vanniktech.maven.publish") { - mavenPublish { - sonatypeHost = "S01" - } - } - - plugins.withId("com.vanniktech.maven.base") { - group = "io.github.triniwiz" - archivesBaseName = "fancycamera" - version = "3.0.8" - - mavenPublishing { - publishToMavenCentral("DEFAULT") - - // Will only apply to non snapshot builds. - // Uses credentials as described above, supports both regular and in memory signing. - signAllPublications() - - pom { - name = "FancyCamera" - packaging = 'aar' - description = "Fancy Android Camera" - inceptionYear = "2019" - url = 'https://github.com/triniwiz/fancycamera' - - licenses { - license { - name = "The Apache License, Version 2.0" - url = "http://www.apache.org/licenses/LICENSE-2.0.txt" - distribution = "http://www.apache.org/licenses/LICENSE-2.0.txt" - } - } - - developers { - developer { - id = 'triniwiz' - name = 'Osei Fortune' - email = 'fortune.osei@yahoo.com' - url = "https://github.com/triniwiz/" - } - } - - scm { - connection 'scm:git@github.com:triniwiz/fancycamera.git' - developerConnection 'scm:git@github.com:triniwiz/fancycamera.git' - url 'https://github.com/triniwiz/fancycamera' - } - } - - pomFromGradleProperties() - } - } -} diff --git a/fancycamera/src/main/AndroidManifest.xml b/fancycamera/src/main/AndroidManifest.xml index c018780..caac036 100644 --- a/fancycamera/src/main/AndroidManifest.xml +++ b/fancycamera/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ + xmlns:tools="http://schemas.android.com/tools"> diff --git a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/Camera.kt b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/Camera.kt index 566c15e..b671a60 100644 --- a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/Camera.kt +++ b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/Camera.kt @@ -14,6 +14,8 @@ import android.util.Log import android.view.ScaleGestureDetector import android.view.Surface import android.view.TextureView +import androidx.databinding.ObservableArrayList +import androidx.databinding.ObservableList import androidx.exifinterface.media.ExifInterface import com.google.android.gms.tasks.Task import com.google.android.gms.tasks.TaskCompletionSource @@ -24,6 +26,7 @@ import java.text.ParseException import java.text.SimpleDateFormat import java.util.* import java.util.concurrent.Executors +import kotlin.collections.ArrayList import kotlin.math.ceil import kotlin.math.log10 import kotlin.math.max @@ -39,6 +42,7 @@ class Camera @JvmOverloads constructor( var isRecording = false var isForceStopping = false var isStarted = false + override var defaultLens: CameraLens = CameraLens.telephoto private fun handleZoom() { synchronized(lock) { if (camera != null) { @@ -72,9 +76,11 @@ class Camera @JvmOverloads constructor( value > 1 -> { 1f } + value < 0 -> { 0f } + else -> { value } @@ -97,7 +103,6 @@ class Camera @JvmOverloads constructor( } } private var cameraExecutor = Executors.newSingleThreadExecutor() - private var imageAnalysisExecutor = Executors.newSingleThreadExecutor() override var displayRatio = "4:3" set(value) { if (cachedPreviewRatioSizeMap.containsKey(value)) { @@ -167,343 +172,9 @@ class Camera @JvmOverloads constructor( private var previewView: TextureView = TextureView(context, attrs, defStyleAttr) - private fun handleBarcodeScanning(data: ByteArray, camera: Camera): Task? { - if (!isBarcodeScanningSupported || !(detectorType == DetectorType.Barcode || detectorType == DetectorType.All)) { - return null - } - val BarcodeScannerClazz = - Class.forName("io.github.triniwiz.fancycamera.barcodescanning.BarcodeScanner") - val barcodeScanner = BarcodeScannerClazz.newInstance() - val BarcodeScannerOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.barcodescanning.BarcodeScanner\$Options") - - val processImageMethod = BarcodeScannerClazz.getDeclaredMethod( - "processBytes", - ByteArray::class.java, - Int::class.java, - Int::class.java, - Int::class.java, - Int::class.java, - BarcodeScannerOptionsClazz - ) - val previewSize = camera.parameters.previewSize - val returnTask = TaskCompletionSource() - - if (barcodeScannerOptions == null) { - barcodeScannerOptions = BarcodeScannerOptionsClazz.newInstance() - } - - val task = processImageMethod.invoke( - barcodeScanner, - data, - previewSize.width, - previewSize.height, - rotationAngle, - ImageFormat.NV21, - barcodeScannerOptions!! - ) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it.isNotEmpty()) { - mainHandler.post { - onBarcodeScanningListener?.onSuccess(it) - } - } - }.addOnFailureListener(imageAnalysisExecutor) { - mainHandler.post { - onBarcodeScanningListener?.onError( - it.message - ?: "Failed to complete face detection.", it - ) - } - }.addOnCompleteListener(imageAnalysisExecutor) { - returnTask.setResult(true) - } - return returnTask.task - } - - private fun handleFaceDetection(data: ByteArray, camera: Camera): Task? { - if (!isFaceDetectionSupported || !(detectorType == DetectorType.Face || detectorType == DetectorType.All)) { - return null - } - val FaceDetectionClazz = - Class.forName("io.github.triniwiz.fancycamera.facedetection.FaceDetection") - val faceDetection = FaceDetectionClazz.newInstance() - val FaceDetectionOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.facedetection.FaceDetection\$Options") - val processBytesMethod = FaceDetectionClazz.getDeclaredMethod( - "processBytes", - ByteArray::class.java, - Int::class.java, - Int::class.java, - Int::class.java, - Int::class.java, - FaceDetectionOptionsClazz - ) - - if (faceDetectionOptions == null) { - faceDetectionOptions = FaceDetectionOptionsClazz.newInstance() - } - - val previewSize = camera.parameters.previewSize - val returnTask = TaskCompletionSource() - val task = processBytesMethod.invoke( - faceDetection, - data, - previewSize.width, - previewSize.height, - rotationAngle, - ImageFormat.NV21, - faceDetectionOptions!! - ) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it.isNotEmpty()) { - onFacesDetectedListener?.onSuccess(it) - } - }.addOnFailureListener(imageAnalysisExecutor) { - onFacesDetectedListener?.onError( - it.message - ?: "Failed to complete face detection.", it - ) - }.addOnCompleteListener { - returnTask.setResult(true) - } - return returnTask.task - } - - private fun handleImageLabeling(data: ByteArray, camera: Camera): Task? { - if (!isImageLabelingSupported || !(detectorType == DetectorType.Image || detectorType == DetectorType.All)) { - return null - } - val ImageLabelingClazz = - Class.forName("io.github.triniwiz.fancycamera.imagelabeling.ImageLabeling") - val imageLabeling = ImageLabelingClazz.newInstance() - val ImageLabelingOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.imagelabeling.ImageLabeling\$Options") - val processBytesMethod = ImageLabelingClazz.getDeclaredMethod( - "processBytes", - ByteArray::class.java, - Int::class.java, - Int::class.java, - Int::class.java, - Int::class.java, - ImageLabelingOptionsClazz - ) - if (imageLabelingOptions == null) { - imageLabelingOptions = ImageLabelingOptionsClazz.newInstance() - } - - val previewSize = camera.parameters.previewSize - val returnTask = TaskCompletionSource() - val task = processBytesMethod.invoke( - imageLabeling, - data, - previewSize.width, - previewSize.height, - rotationAngle, - ImageFormat.NV21, - imageLabelingOptions!! - ) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it.isNotEmpty()) { - onImageLabelingListener?.onSuccess(it) - } - }.addOnFailureListener(imageAnalysisExecutor) { - onImageLabelingListener?.onError( - it.message - ?: "Failed to complete face detection.", it - ) - }.addOnCompleteListener { - returnTask.setResult(true) - } - return returnTask.task - } - - private fun handleObjectDetection(data: ByteArray, camera: Camera): Task? { - if (!isObjectDetectionSupported || !(detectorType == DetectorType.Object || detectorType == DetectorType.All)) { - return null - } - val ObjectDetectionClazz = - Class.forName("io.github.triniwiz.fancycamera.objectdetection.ObjectDetection") - val objectDetection = ObjectDetectionClazz.newInstance() - val ObjectDetectionOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.objectdetection.ObjectDetection\$Options") - val processBytesMethod = ObjectDetectionClazz.getDeclaredMethod( - "processBytes", - ByteArray::class.java, - Int::class.java, - Int::class.java, - Int::class.java, - Int::class.java, - ObjectDetectionOptionsClazz - ) - - if (objectDetectionOptions == null) { - objectDetectionOptions = ObjectDetectionOptionsClazz.newInstance() - } - - val previewSize = camera.parameters.previewSize - val returnTask = TaskCompletionSource() - val task = processBytesMethod.invoke( - objectDetection, - data, - previewSize.width, - previewSize.height, - rotationAngle, - ImageFormat.NV21, - objectDetectionOptions!! - ) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it.isNotEmpty()) { - onObjectDetectedListener?.onSuccess(it) - } - }.addOnFailureListener(imageAnalysisExecutor) { - onObjectDetectedListener?.onError( - it.message - ?: "Failed to complete face detection.", it - ) - }.addOnCompleteListener { - returnTask.setResult(true) - } - return returnTask.task - } - - private fun handlePoseDetection(data: ByteArray, camera: Camera): Task? { - if (!isFaceDetectionSupported || !(detectorType == DetectorType.Face || detectorType == DetectorType.All)) { - return null - } - val PoseDetectionClazz = - Class.forName("io.github.triniwiz.fancycamera.posedetection.PoseDetection") - val poseDetection = PoseDetectionClazz.newInstance() - val processBytesMethod = PoseDetectionClazz.getDeclaredMethod( - "processBytes", - ByteArray::class.java, - Int::class.java, - Int::class.java, - Int::class.java, - Int::class.java - ) - val previewSize = camera.parameters.previewSize - val returnTask = TaskCompletionSource() - val task = processBytesMethod.invoke( - poseDetection, - data, - previewSize.width, - previewSize.height, - rotationAngle, - ImageFormat.NV21 - ) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it.isNotEmpty()) { - onPoseDetectedListener?.onSuccess(it) - } - }.addOnFailureListener(imageAnalysisExecutor) { - onPoseDetectedListener?.onError( - it.message - ?: "Failed to complete face detection.", it - ) - }.addOnCompleteListener { - returnTask.setResult(true) - } - return returnTask.task - } - - private fun handleTextRecognition(data: ByteArray, camera: Camera): Task? { - if (!isTextRecognitionSupported || !(detectorType == DetectorType.Text || detectorType == DetectorType.All)) { - return null - } - val TextRecognitionClazz = - Class.forName("io.github.triniwiz.fancycamera.textrecognition.TextRecognition") - val textRecognition = TextRecognitionClazz.newInstance() - val processBytesMethod = TextRecognitionClazz.getDeclaredMethod( - "processBytes", - ByteArray::class.java, - Int::class.java, - Int::class.java, - Int::class.java, - Int::class.java - ) - val previewSize = camera.parameters.previewSize - val returnTask = TaskCompletionSource() - val task = processBytesMethod.invoke( - textRecognition, - data, - previewSize.width, - previewSize.height, - rotationAngle, - ImageFormat.NV21 - ) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it.isNotEmpty()) { - mainHandler.post { - onTextRecognitionListener?.onSuccess(it) - } - } - }.addOnFailureListener(imageAnalysisExecutor) { - mainHandler.post { - onTextRecognitionListener?.onError( - it.message - ?: "Failed to complete text recognition.", it - ) - } - }.addOnCompleteListener { - returnTask.setResult(true) - } - return returnTask.task - } - - private fun handleSelfieSegmentation(data: ByteArray, camera: Camera): Task? { - if (!isSelfieSegmentationSupported || !(detectorType == DetectorType.Selfie || detectorType == DetectorType.All)) { - return null - } - val SelfieSegmentationClazz = - Class.forName("io.github.triniwiz.fancycamera.selfiesegmentation.SelfieSegmentation") - val selfieSegmentation = SelfieSegmentationClazz.newInstance() - val SelfieSegmentationOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.selfiesegmentation.SelfieSegmentation\$Options") - val processBytesMethod = SelfieSegmentationClazz.getDeclaredMethod( - "processBytes", - ByteArray::class.java, - Int::class.java, - Int::class.java, - Int::class.java, - Int::class.java, - SelfieSegmentationOptionsClazz - ) - - if (selfieSegmentationOptions == null) { - selfieSegmentationOptions = SelfieSegmentationOptionsClazz.newInstance() - } - - val previewSize = camera.parameters.previewSize - val returnTask = TaskCompletionSource() - val task = processBytesMethod.invoke( - selfieSegmentation, - data, - previewSize.width, - previewSize.height, - rotationAngle, - ImageFormat.NV21, - selfieSegmentationOptions!! - ) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it != null) { - onImageLabelingListener?.onSuccess(it) - } - }.addOnFailureListener(imageAnalysisExecutor) { - onImageLabelingListener?.onError( - it.message - ?: "Failed to complete face detection.", it - ) - }.addOnCompleteListener { - returnTask.setResult(true) - } - return returnTask.task - } - init { handlePinchZoom() addView(previewView) - detectSupport() previewView.surfaceTextureListener = object : TextureView.SurfaceTextureListener { override fun onSurfaceTextureAvailable( surface: SurfaceTexture, @@ -543,12 +214,16 @@ class Camera @JvmOverloads constructor( when (value) { CameraFlashMode.OFF -> camera?.parameters?.flashMode = Camera.Parameters.FLASH_MODE_OFF + CameraFlashMode.ON -> camera?.parameters?.flashMode = Camera.Parameters.FLASH_MODE_ON + CameraFlashMode.AUTO -> camera?.parameters?.flashMode = Camera.Parameters.FLASH_MODE_AUTO + CameraFlashMode.RED_EYE -> camera?.parameters?.flashMode = Camera.Parameters.FLASH_MODE_RED_EYE + CameraFlashMode.TORCH -> camera?.parameters?.flashMode = Camera.Parameters.FLASH_MODE_TORCH } @@ -556,7 +231,51 @@ class Camera @JvmOverloads constructor( } } - override var detectorType: DetectorType = DetectorType.None + override var imageProcessors: ObservableList> = + ObservableArrayList>() + .apply { + val callback: ObservableList.OnListChangedCallback>> = + object : + ObservableList.OnListChangedCallback>>() { + override fun onChanged(sender: ObservableList>?) {} + + override fun onItemRangeChanged( + sender: ObservableList>?, + positionStart: Int, + itemCount: Int + ) { + // noop + } + + override fun onItemRangeInserted( + sender: ObservableList>?, + positionStart: Int, + itemCount: Int + ) { + // noop + } + + override fun onItemRangeMoved( + sender: ObservableList>?, + fromPosition: Int, + toPosition: Int, + itemCount: Int + ) { + // noop + } + + override fun onItemRangeRemoved( + sender: ObservableList>?, + positionStart: Int, + itemCount: Int + ) { + if (sender?.isEmpty() == true) { + } + } + } + addOnListChangedCallback(callback) + } + override var allowExifRotation: Boolean = true // TODO( Implement ) @@ -677,9 +396,9 @@ class Camera @JvmOverloads constructor( } if (key != null) { - val list = cachedPictureRatioSizeMap.get(key) + val list = cachedPictureRatioSizeMap[key] if (list == null) { - cachedPictureRatioSizeMap.put(key, mutableListOf(value)) + cachedPictureRatioSizeMap[key] = mutableListOf(value) } else { list.add(value) } @@ -717,36 +436,43 @@ class Camera @JvmOverloads constructor( WhiteBalance.Auto -> { parameters.whiteBalance = WhiteBalance.Auto.value } + WhiteBalance.Sunny -> { if (supportedWhiteBalance.contains(WhiteBalance.Sunny.value)) { parameters.whiteBalance = WhiteBalance.Sunny.value } } + WhiteBalance.Cloudy -> { if (supportedWhiteBalance.contains(WhiteBalance.Cloudy.value)) { parameters.whiteBalance = WhiteBalance.Cloudy.value } } + WhiteBalance.Shadow -> { if (supportedWhiteBalance.contains(WhiteBalance.Shadow.value)) { parameters.whiteBalance = WhiteBalance.Shadow.value } } + WhiteBalance.Twilight -> { if (supportedWhiteBalance.contains(WhiteBalance.Twilight.value)) { parameters.whiteBalance = WhiteBalance.Twilight.value } } + WhiteBalance.Fluorescent -> { if (supportedWhiteBalance.contains(WhiteBalance.Fluorescent.value)) { parameters.whiteBalance = WhiteBalance.Fluorescent.value } } + WhiteBalance.Incandescent -> { if (supportedWhiteBalance.contains(WhiteBalance.Incandescent.value)) { parameters.whiteBalance = WhiteBalance.Incandescent.value } } + WhiteBalance.WarmFluorescent -> { if (supportedWhiteBalance.contains(WhiteBalance.WarmFluorescent.value)) { parameters.whiteBalance = WhiteBalance.WarmFluorescent.value @@ -756,7 +482,8 @@ class Camera @JvmOverloads constructor( } val pictureSize = stringSizeToSize(pictureSize) val previewSize = cachedPreviewRatioSizeMap[displayRatio]?.get(0) ?: Size(0, 0) - if (isMLSupported) { + + if (imageProcessors.isNotEmpty()) { parameters?.setPreviewSize(previewSize.width, previewSize.height) parameters?.setPictureSize(pictureSize.width, pictureSize.height) parameters?.previewFormat = ImageFormat.NV21 @@ -765,8 +492,22 @@ class Camera @JvmOverloads constructor( camera?.addCallbackBuffer(previewBuffer!!.array()) camera?.setPreviewCallbackWithBuffer { data, camera -> if (data != null) { - if (currentFrame != processEveryNthFrame) { + if (currentFrame < processEveryNthFrame) { incrementCurrentFrame() + + try { + val size = camera.parameters.previewSize + camera.addCallbackBuffer( + createPreviewBuffer( + Size( + size.width, + size.height + ) + ) + ) + } catch (_: Exception) { + } + return@setPreviewCallbackWithBuffer } @@ -780,53 +521,117 @@ class Camera @JvmOverloads constructor( ) } - Log.d("com.test", "setPreviewCallbackWithBuffer : ${data.size}") - - - val tasks = mutableListOf>() - //BarcodeScanner - val barcodeTask = handleBarcodeScanning(data, camera) - if (barcodeTask != null) { - tasks.add(barcodeTask) - } - // FaceDetection - val faceTask = handleFaceDetection(data, camera) - if (faceTask != null) { - tasks.add(faceTask) - } - - //ImageLabeling - val imageTask = handleImageLabeling(data, camera) - if (imageTask != null) { - tasks.add(imageTask) - } - - //ObjectDetection - val objectTask = handleObjectDetection(data, camera) - if (objectTask != null) { - tasks.add(objectTask) - } - - //PoseDetection - val poseTask = handlePoseDetection(data, camera) - if (poseTask != null) { - tasks.add(poseTask) - } - - // TextRecognition - val textTask = handleTextRecognition(data, camera) - if (textTask != null) { - tasks.add(textTask) - } - // SelfieSegmentation - val selfieTask = handleSelfieSegmentation(data, camera) - if (selfieTask != null) { - tasks.add(selfieTask) - } + if (imageProcessors.isNotEmpty()) { + cameraExecutor.execute { + for (processor in imageProcessors) { + val process = processor.process( + data, + camera.parameters.previewSize.width, + camera.parameters.previewSize.height, + rotationAngle, + ImageFormat.NV21 + ) + try { + process.run() + val result = process.get() + + if ((0..6).contains(processor.type) && result is String && result.isEmpty()) { + continue + } + + if (result != null) { + when (processor.type) { + 0 -> { + onBarcodeScanningListener?.onSuccess(result) + } + + 1 -> { + onFacesDetectedListener?.onSuccess(result) + } + + 2 -> { + onImageLabelingListener?.onSuccess(result) + } + + 3 -> { + onObjectDetectedListener?.onSuccess(result) + } + + 4 -> { + onPoseDetectedListener?.onSuccess(result) + } + + 5 -> { + onSelfieSegmentationListener?.onSuccess(result) + } + + 6 -> { + onTextRecognitionListener?.onSuccess(result) + } + + else -> {} + } + } + } catch (e: Exception) { + when (processor.type) { + 0 -> { + onBarcodeScanningListener?.onError( + e.message + ?: "Failed to complete barcode scanning.", e + ) + } + + 1 -> { + onFacesDetectedListener?.onError( + e.message + ?: "Failed to complete face detection.", e + ) + } + + 2 -> { + onImageLabelingListener?.onError( + e.message + ?: "Failed to complete image label detection.", + e + ) + } + + 3 -> { + onObjectDetectedListener?.onError( + e.message + ?: "Failed to complete object detection.", e + ) + } + + 4 -> { + onPoseDetectedListener?.onError( + e.message + ?: "Failed to complete pose detection.", e + ) + } + + 5 -> { + onSelfieSegmentationListener?.onError( + e.message + ?: "Failed to complete selfie segmentation detection.", + e + ) + } + + 6 -> { + onTextRecognitionListener?.onError( + e.message + ?: "Failed to complete selfie text recognition.", + e + ) + } + } + } + } + } - if (tasks.isNotEmpty()) { - Tasks.whenAllComplete(tasks).addOnCompleteListener { + cameraExecutor.execute { try { val size = camera.parameters.previewSize camera.addCallbackBuffer( @@ -837,11 +642,13 @@ class Camera @JvmOverloads constructor( ) ) ) - } catch (e: java.lang.Exception) { + } catch (_: Exception) { } finally { resetCurrentFrame() } } + } else { + resetCurrentFrame() } } } @@ -970,9 +777,11 @@ class Camera @JvmOverloads constructor( 1 -> { orientation = 2 } + 3 -> { orientation = 4 } + 6 -> { orientation = 7 } @@ -981,21 +790,25 @@ class Camera @JvmOverloads constructor( when (orientation) { 1 -> { } + 2 -> matrix.postScale(-1f, 1f) 3 -> matrix.postRotate(180f) 4 -> { matrix.postRotate(180f) matrix.postScale(-1f, 1f) } + 5 -> { matrix.postRotate(90f) matrix.postScale(-1f, 1f) } + 6 -> matrix.postRotate(90f) 7 -> { matrix.postRotate(270f) matrix.postScale(-1f, 1f) } + 8 -> matrix.postRotate(270f) } @@ -1151,6 +964,7 @@ class Camera @JvmOverloads constructor( "Server died", java.lang.Exception("Server died") ) + MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN -> listener?.onCameraError( "Unknown", java.lang.Exception("Unknown") diff --git a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/Camera2.kt b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/Camera2.kt index 6ff65bf..888cb99 100644 --- a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/Camera2.kt +++ b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/Camera2.kt @@ -1,17 +1,18 @@ package io.github.triniwiz.fancycamera import android.annotation.SuppressLint +import android.app.Activity import android.content.ContentValues import android.content.Context import android.content.res.Configuration import android.graphics.* import android.hardware.camera2.* -import android.media.Image import android.os.Build import android.os.Environment import android.provider.MediaStore import android.util.AttributeSet import android.util.Log +import android.util.Range import android.view.GestureDetector import android.view.MotionEvent import android.view.ScaleGestureDetector @@ -21,17 +22,19 @@ import androidx.camera.camera2.interop.Camera2CameraInfo import androidx.camera.camera2.interop.Camera2Interop import androidx.camera.core.* import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST +import androidx.camera.core.resolutionselector.AspectRatioStrategy +import androidx.camera.core.resolutionselector.ResolutionSelector +import androidx.camera.core.resolutionselector.ResolutionStrategy import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.video.* -import androidx.camera.video.VideoCapture import androidx.camera.view.PreviewView import androidx.core.content.ContextCompat import androidx.core.view.GestureDetectorCompat +import androidx.databinding.ObservableArrayList +import androidx.databinding.ObservableList +import androidx.databinding.ObservableList.OnListChangedCallback import androidx.exifinterface.media.ExifInterface import androidx.lifecycle.LifecycleOwner -import com.google.android.gms.tasks.Task -import com.google.android.gms.tasks.TaskCompletionSource -import com.google.android.gms.tasks.Tasks import com.google.common.util.concurrent.ListenableFuture import java.io.File import java.io.FileInputStream @@ -42,6 +45,7 @@ import java.text.SimpleDateFormat import java.util.* import java.util.concurrent.Executors import java.util.concurrent.TimeUnit +import kotlin.collections.HashMap @SuppressLint("UnsafeOptInUsageError") @@ -54,12 +58,9 @@ class Camera2 @JvmOverloads constructor( private var imageCapture: ImageCapture? = null private var imageAnalysis: androidx.camera.core.ImageAnalysis? = null private var videoCapture: VideoCapture? = null - private var imageAnalysisExecutor = Executors.newSingleThreadExecutor() - private var imageCaptureExecutor = Executors.newSingleThreadExecutor() - private var videoCaptureExecutor = Executors.newSingleThreadExecutor() + private var executor = Executors.newSingleThreadExecutor() private var camera: androidx.camera.core.Camera? = null private var preview: Preview? = null - private var surfaceRequest: SurfaceRequest? = null private var isStarted = false private var isRecording = false private var file: File? = null @@ -74,6 +75,56 @@ class Camera2 @JvmOverloads constructor( override var enablePinchZoom: Boolean = true override var enableTapToFocus: Boolean = true + override var imageProcessors: ObservableList> = + ObservableArrayList>() + .apply { + val callback: OnListChangedCallback>> = + object : OnListChangedCallback>>() { + override fun onChanged(sender: ObservableList>?) {} + + override fun onItemRangeChanged( + sender: ObservableList>?, + positionStart: Int, + itemCount: Int + ) { + // noop + } + + override fun onItemRangeInserted( + sender: ObservableList>?, + positionStart: Int, + itemCount: Int + ) { + // noop + } + + override fun onItemRangeMoved( + sender: ObservableList>?, + fromPosition: Int, + toPosition: Int, + itemCount: Int + ) { + // noop + } + + override fun onItemRangeRemoved( + sender: ObservableList>?, + positionStart: Int, + itemCount: Int + ) { + if (sender?.isEmpty() == true) { + + } + } + } + addOnListChangedCallback(callback) + } + override var defaultLens: CameraLens = CameraLens.telephoto + set(value) { + field = value + refreshCamera() + } + override var retrieveLatestImage: Boolean = false set(value) { field = value @@ -101,7 +152,7 @@ class Camera2 @JvmOverloads constructor( if (storedZoom > 0) { it.setLinearZoom(storedZoom) storedZoom = -1f - } else if(storedZoomRatio > 0) { + } else if (storedZoomRatio > 0) { it.setZoomRatio(storedZoomRatio) storedZoomRatio = -1f } else { @@ -119,10 +170,11 @@ class Camera2 @JvmOverloads constructor( return previewView } val maxZoomRatio: Float - get() = camera?.cameraInfo?.zoomState?.value?.maxZoomRatio ?: 1f; + get() = camera?.cameraInfo?.zoomState?.value?.maxZoomRatio ?: 1f val minZoomRatio: Float - get() = camera?.cameraInfo?.zoomState?.value?.minZoomRatio ?: 1f; - var storedZoomRatio: Float = -1F; + get() = camera?.cameraInfo?.zoomState?.value?.minZoomRatio ?: 1f + + var storedZoomRatio: Float = -1F override var zoomRatio: Float get() = camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 1f set(value) { @@ -134,15 +186,18 @@ class Camera2 @JvmOverloads constructor( var storedZoom: Float = -1.0F override var zoom: Float - get() = camera?.cameraInfo?.zoomState?.value?.linearZoom ?: ( if (storedZoom < 0) 0.0f else storedZoom) + get() = camera?.cameraInfo?.zoomState?.value?.linearZoom + ?: (if (storedZoom < 0) 0.0f else storedZoom) set(value) { storedZoom = when { value > 1 -> { 1f } + value < 0 -> { 0f } + else -> { value } @@ -152,8 +207,8 @@ class Camera2 @JvmOverloads constructor( } override var whiteBalance: WhiteBalance = WhiteBalance.Auto set(value) { + field = value if (!isRecording) { - field = value refreshCamera() } } @@ -164,6 +219,7 @@ class Camera2 @JvmOverloads constructor( "16:9" -> { value } + "4:3" -> value else -> return } @@ -194,313 +250,6 @@ class Camera2 @JvmOverloads constructor( private var previewView: PreviewView = PreviewView(context, attrs, defStyleAttr) - @SuppressLint("UnsafeOptInUsageError") - private fun handleBarcodeScanning(proxy: ImageProxy): Task? { - if (!isBarcodeScanningSupported || !(detectorType == DetectorType.Barcode || detectorType == DetectorType.All)) { - return null - } - val image = proxy.image ?: return null - val rotationAngle = proxy.imageInfo.rotationDegrees - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val BarcodeScannerClazz = - Class.forName("io.github.triniwiz.fancycamera.barcodescanning.BarcodeScanner") - val barcodeScanner = BarcodeScannerClazz.newInstance() - val fromMediaImage = - InputImageClazz.getMethod("fromMediaImage", Image::class.java, Int::class.java) - val inputImage = fromMediaImage.invoke(null, image, rotationAngle) - val BarcodeScannerOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.barcodescanning.BarcodeScanner\$Options") - val processImageMethod = BarcodeScannerClazz.getDeclaredMethod( - "processImage", - InputImageClazz, - BarcodeScannerOptionsClazz - ) - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke( - barcodeScanner, - inputImage, - barcodeScannerOptions!! - ) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it.isNotEmpty()) { - mainHandler.post { - onBarcodeScanningListener?.onSuccess(it) - } - } - }.addOnFailureListener(imageAnalysisExecutor) { - mainHandler.post { - onBarcodeScanningListener?.onError( - it.message - ?: "Failed to complete face detection.", it - ) - } - }.addOnCompleteListener(imageAnalysisExecutor) { - returnTask.setResult(true) - } - return returnTask.task - } - - @SuppressLint("UnsafeOptInUsageError") - private fun handleFaceDetection(proxy: ImageProxy): Task? { - if (!isFaceDetectionSupported || !(detectorType == DetectorType.Face || detectorType == DetectorType.All)) { - return null - } - val image = proxy.image ?: return null - val rotationAngle = proxy.imageInfo.rotationDegrees - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val FaceDetectionClazz = - Class.forName("io.github.triniwiz.fancycamera.facedetection.FaceDetection") - val faceDetection = FaceDetectionClazz.newInstance() - val fromMediaImage = - InputImageClazz.getMethod("fromMediaImage", Image::class.java, Int::class.java) - val inputImage = fromMediaImage.invoke(null, image, rotationAngle) - val FaceDetectionOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.facedetection.FaceDetection\$Options") - val processImageMethod = FaceDetectionClazz.getDeclaredMethod( - "processImage", - InputImageClazz, - FaceDetectionOptionsClazz - ) - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke( - faceDetection, - inputImage, - faceDetectionOptions!! - ) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it.isNotEmpty()) { - mainHandler.post { - onFacesDetectedListener?.onSuccess(it) - } - } - }.addOnFailureListener(imageAnalysisExecutor) { - mainHandler.post { - onFacesDetectedListener?.onError( - it.message - ?: "Failed to complete face detection.", it - ) - } - }.addOnCompleteListener(imageAnalysisExecutor) { - returnTask.setResult(true) - } - return returnTask.task - } - - @SuppressLint("UnsafeOptInUsageError") - private fun handleImageLabeling(proxy: ImageProxy): Task? { - if (!isImageLabelingSupported || !(detectorType == DetectorType.Image || detectorType == DetectorType.All)) { - return null - } - val image = proxy.image ?: return null - val rotationAngle = proxy.imageInfo.rotationDegrees - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val ImageLabelingClazz = - Class.forName("io.github.triniwiz.fancycamera.imagelabeling.ImageLabeling") - val imageLabeling = ImageLabelingClazz.newInstance() - val fromMediaImage = - InputImageClazz.getMethod("fromMediaImage", Image::class.java, Int::class.java) - val inputImage = fromMediaImage.invoke(null, image, rotationAngle) - val ImageLabelingOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.imagelabeling.ImageLabeling\$Options") - val processImageMethod = ImageLabelingClazz.getDeclaredMethod( - "processImage", - InputImageClazz, - ImageLabelingOptionsClazz - ) - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke( - imageLabeling, - inputImage, - imageLabelingOptions!! - ) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it.isNotEmpty()) { - mainHandler.post { - onImageLabelingListener?.onSuccess(it) - } - } - }.addOnFailureListener(imageAnalysisExecutor) { - mainHandler.post { - onImageLabelingListener?.onError( - it.message - ?: "Failed to complete face detection.", it - ) - } - }.addOnCompleteListener(imageAnalysisExecutor) { - returnTask.setResult(true) - } - return returnTask.task - } - - @SuppressLint("UnsafeOptInUsageError") - private fun handleObjectDetection(proxy: ImageProxy): Task? { - if (!isObjectDetectionSupported || !(detectorType == DetectorType.Object || detectorType == DetectorType.All)) { - return null - } - val image = proxy.image ?: return null - val rotationAngle = proxy.imageInfo.rotationDegrees - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val ObjectDetectionClazz = - Class.forName("io.github.triniwiz.fancycamera.objectdetection.ObjectDetection") - val objectDetection = ObjectDetectionClazz.newInstance() - val fromMediaImage = - InputImageClazz.getMethod("fromMediaImage", Image::class.java, Int::class.java) - val inputImage = fromMediaImage.invoke(null, image, rotationAngle) - val ObjectDetectionOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.objectdetection.ObjectDetection\$Options") - val processImageMethod = ObjectDetectionClazz.getDeclaredMethod( - "processImage", - InputImageClazz, - ObjectDetectionOptionsClazz - ) - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke( - objectDetection, - inputImage, - objectDetectionOptions!! - ) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it.isNotEmpty()) { - mainHandler.post { - onObjectDetectedListener?.onSuccess(it) - } - } - }.addOnFailureListener(imageAnalysisExecutor) { - mainHandler.post { - onObjectDetectedListener?.onError( - it.message - ?: "Failed to complete face detection.", it - ) - } - }.addOnCompleteListener(imageAnalysisExecutor) { - returnTask.setResult(true) - } - return returnTask.task - } - - @SuppressLint("UnsafeOptInUsageError") - private fun handlePoseDetection(proxy: ImageProxy): Task? { - if (!isPoseDetectionSupported || !(detectorType == DetectorType.Pose || detectorType == DetectorType.All)) { - return null - } - val image = proxy.image ?: return null - val rotationAngle = proxy.imageInfo.rotationDegrees - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val PoseDetectionClazz = - Class.forName("io.github.triniwiz.fancycamera.posedetection.PoseDetection") - val poseDetection = PoseDetectionClazz.newInstance() - val fromMediaImage = - InputImageClazz.getMethod("fromMediaImage", Image::class.java, Int::class.java) - val inputImage = fromMediaImage.invoke(null, image, rotationAngle) - val processImageMethod = - PoseDetectionClazz.getDeclaredMethod("processImage", InputImageClazz) - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke(poseDetection, inputImage) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it.isNotEmpty()) { - mainHandler.post { - onPoseDetectedListener?.onSuccess(it) - } - } - }.addOnFailureListener(imageAnalysisExecutor) { - mainHandler.post { - onPoseDetectedListener?.onError( - it.message - ?: "Failed to complete text recognition.", it - ) - } - }.addOnCompleteListener(imageAnalysisExecutor) { - returnTask.setResult(true) - } - return returnTask.task - } - - @SuppressLint("UnsafeOptInUsageError") - private fun handleTextRecognition(proxy: ImageProxy): Task? { - if (!isTextRecognitionSupported || !(detectorType == DetectorType.Text || detectorType == DetectorType.All)) { - return null - } - val image = proxy.image ?: return null - val rotationAngle = proxy.imageInfo.rotationDegrees - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val TextRecognitionClazz = - Class.forName("io.github.triniwiz.fancycamera.textrecognition.TextRecognition") - val textRecognition = TextRecognitionClazz.newInstance() - val fromMediaImage = - InputImageClazz.getMethod("fromMediaImage", Image::class.java, Int::class.java) - val inputImage = fromMediaImage.invoke(null, image, rotationAngle) - val processImageMethod = - TextRecognitionClazz.getDeclaredMethod("processImage", InputImageClazz) - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke(textRecognition, inputImage) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it.isNotEmpty()) { - mainHandler.post { - onTextRecognitionListener?.onSuccess(it) - } - } - }.addOnFailureListener(imageAnalysisExecutor) { - mainHandler.post { - onTextRecognitionListener?.onError( - it.message - ?: "Failed to complete text recognition.", it - ) - } - }.addOnCompleteListener(imageAnalysisExecutor) { - returnTask.setResult(true) - } - return returnTask.task - } - - @SuppressLint("UnsafeOptInUsageError") - private fun handleSelfieSegmentation(proxy: ImageProxy): Task? { - if (!isSelfieSegmentationSupported || !(detectorType == DetectorType.Selfie || detectorType == DetectorType.All)) { - return null - } - val image = proxy.image ?: return null - val rotationAngle = proxy.imageInfo.rotationDegrees - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val SelfieSegmentationClazz = - Class.forName("io.github.triniwiz.fancycamera.selfiesegmentation.SelfieSegmentation") - val selfieSegmentation = SelfieSegmentationClazz.newInstance() - val fromMediaImage = - InputImageClazz.getMethod("fromMediaImage", Image::class.java, Int::class.java) - val inputImage = fromMediaImage.invoke(null, image, rotationAngle) - - val SelfieSegmentationOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.selfiesegmentation.SelfieSegmentation\$Options") - - val processImageMethod = SelfieSegmentationClazz.getDeclaredMethod( - "processImage", - InputImageClazz, - SelfieSegmentationOptionsClazz - ) - - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke( - selfieSegmentation, - inputImage, - selfieSegmentationOptions - ) as Task - task.addOnSuccessListener(imageAnalysisExecutor) { - if (it != null) { - mainHandler.post { - onSelfieSegmentationListener?.onSuccess(it) - } - } - }.addOnFailureListener(imageAnalysisExecutor) { - mainHandler.post { - onSelfieSegmentationListener?.onError( - it.message - ?: "Failed to complete text recognition.", it - ) - } - }.addOnCompleteListener(imageAnalysisExecutor) { - returnTask.setResult(true) - } - return returnTask.task - } - private fun getFocusMeteringActions(): Int { var actions = FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE if (whiteBalance == WhiteBalance.Auto) { @@ -515,9 +264,12 @@ class Camera2 @JvmOverloads constructor( autoFocusTimer = null } + private var scaleFactor = 1F private fun setupGestureListeners() { + val listener = - object : ScaleGestureDetector.SimpleOnScaleGestureListener(), GestureDetector.OnGestureListener { + object : ScaleGestureDetector.SimpleOnScaleGestureListener(), + GestureDetector.OnGestureListener { override fun onScale(detector: ScaleGestureDetector): Boolean { camera?.cameraInfo?.zoomState?.value?.let { zoomState -> camera?.cameraControl?.setZoomRatio( @@ -560,16 +312,16 @@ class Camera2 @JvmOverloads constructor( } override fun onScroll( - p0: MotionEvent, + p0: MotionEvent?, p1: MotionEvent, p2: Float, p3: Float - ): Boolean = false + ): Boolean = false override fun onLongPress(p0: MotionEvent) = Unit override fun onFling( - p0: MotionEvent, + p0: MotionEvent?, p1: MotionEvent, p2: Float, p3: Float @@ -593,8 +345,6 @@ class Camera2 @JvmOverloads constructor( handleAutoFocus() } addView(previewView) - detectSupport() - initOptions() // TODO: Bind this to the view's onCreate method cameraProviderFuture = ProcessCameraProvider.getInstance(context) @@ -613,7 +363,7 @@ class Camera2 @JvmOverloads constructor( private fun handleAutoFocus() { if (camera?.cameraControl == null) { pendingAutoFocus = true - return; + return } pendingAutoFocus = false; if (autoFocus) { @@ -624,16 +374,19 @@ class Camera2 @JvmOverloads constructor( val centerHeight = previewView.height.toFloat() / 2 val autoFocusPoint = factory.createPoint(centerWidth, centerHeight) try { - val action =FocusMeteringAction.Builder( + val action = FocusMeteringAction.Builder( autoFocusPoint, getFocusMeteringActions() ).apply { setAutoCancelDuration(2, TimeUnit.SECONDS) }.build(); val supported = camera?.cameraInfo?.isFocusMeteringSupported(action) - camera?.cameraControl?.startFocusAndMetering( - action - ) + if (supported == true) { + camera?.cameraControl?.startFocusAndMetering( + action + ) + } + } catch (_: CameraInfoUnavailableException) { } } @@ -648,38 +401,6 @@ class Camera2 @JvmOverloads constructor( override var maxVideoFrameRate: Int = -1 override var disableHEVC: Boolean = false - - override var detectorType: DetectorType = DetectorType.None - @SuppressLint("UnsafeOptInUsageError") - set(value) { - field = value - if (!isRecording) { - if (imageAnalysis == null) { - setUpAnalysis() - } - if (value == DetectorType.None) { - if (cameraProvider?.isBound(imageAnalysis!!) == true) { - cameraProvider?.unbind(imageAnalysis!!) - } - } else { - videoCapture?.let { - if (cameraProvider?.isBound(it) == true) { - cameraProvider?.unbind(it) - } - } - - if (cameraProvider?.isBound(imageAnalysis!!) == false) { - camera = cameraProvider?.bindToLifecycle( - context as LifecycleOwner, - selectorFromPosition(), - imageAnalysis - ) - handleAutoFocus() - } - } - } - } - override val numberOfCameras: Int get() { if (cameraManager == null) { @@ -694,7 +415,10 @@ class Camera2 @JvmOverloads constructor( } private fun getFlashMode(): Int { - var test = camera?.cameraInfo?.hasFlashUnit(); + val test = camera?.cameraInfo?.hasFlashUnit() + if (test != null && !test) { + return ImageCapture.FLASH_MODE_OFF + } return when (flashMode) { CameraFlashMode.AUTO -> ImageCapture.FLASH_MODE_AUTO CameraFlashMode.ON -> ImageCapture.FLASH_MODE_ON @@ -707,10 +431,40 @@ class Camera2 @JvmOverloads constructor( private fun selectorFromPosition(): CameraSelector { return CameraSelector.Builder() .apply { - if (position == CameraPosition.FRONT) { - requireLensFacing(CameraSelector.LENS_FACING_FRONT) - } else { - requireLensFacing(CameraSelector.LENS_FACING_BACK) + addCameraFilter { + it.filter { info -> + if (info.lensFacing == CameraSelector.LENS_FACING_EXTERNAL || info.lensFacing == CameraSelector.LENS_FACING_UNKNOWN) { + false + } else if (position.lenFacing != info.lensFacing) { + false + } else { + when (defaultLens) { + CameraLens.auto -> { + if (info.intrinsicZoomRatio >= 1.0) { + true + } else { + false + } + } + + CameraLens.telephoto -> { + if (info.intrinsicZoomRatio >= 1.0) { + true + } else { + false + } + } + + CameraLens.wide, CameraLens.ultrawide -> { + if (info.intrinsicZoomRatio < 1.0) { + true + } else { + false + } + } + } + } + } } } .build() @@ -743,6 +497,32 @@ class Camera2 @JvmOverloads constructor( } } + private fun bindDefaultUseCase() { + if (imageAnalysis != null && imageProcessors.isNotEmpty()) { + (context as? Activity)?.let { + it.runOnUiThread { + camera = cameraProvider?.bindToLifecycle( + context as LifecycleOwner, + selectorFromPosition(), + imageAnalysis, + preview + ) + handleAutoFocus() + } + } + return + } + (context as? Activity)?.let { + it.runOnUiThread { + camera = cameraProvider?.bindToLifecycle( + context as LifecycleOwner, + selectorFromPosition(), + preview + ) + } + } + } + private fun safeUnbindAll() { try { cameraProvider?.unbindAll() @@ -809,74 +589,123 @@ class Camera2 @JvmOverloads constructor( private fun setUpAnalysis() { val builder = androidx.camera.core.ImageAnalysis.Builder() .apply { - if (getDeviceRotation() > -1) { - setTargetRotation(getDeviceRotation()) - } setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST) } - val extender = Camera2Interop.Extender(builder) imageAnalysis = builder.build() - imageAnalysis?.setAnalyzer(imageAnalysisExecutor) { + imageAnalysis?.setAnalyzer(executor) { proxy -> + proxy.image?.let { image -> + if (currentFrame < processEveryNthFrame) { + incrementCurrentFrame() + proxy.close() + return@setAnalyzer + } - if (it.image != null && currentFrame != processEveryNthFrame) { - incrementCurrentFrame() - return@setAnalyzer - } + if (retrieveLatestImage) { + latestImage = BitmapUtils.getBitmap(proxy) + } - if (retrieveLatestImage) { - latestImage = BitmapUtils.getBitmap(it) - } + if (imageProcessors.isNotEmpty()) { + executor.execute { + for (processor in imageProcessors) { + val process = processor.process(image, proxy.imageInfo.rotationDegrees) + try { + process.run() + val result = process.get() + if (result != null) { + if ((0..6).contains(processor.type) && result is String && result.isEmpty()) { + continue + } + when (processor.type) { + 0 -> { + onBarcodeScanningListener?.onSuccess(result) + return@execute + } - if (it.image != null) { - val tasks = mutableListOf>() - //BarcodeScanning - val barcodeTask = handleBarcodeScanning(it) - if (barcodeTask != null) { - tasks.add(barcodeTask) - } + 1 -> { + onFacesDetectedListener?.onSuccess(result) + } - // FaceDetection - val faceTask = handleFaceDetection(it) - if (faceTask != null) { - tasks.add(faceTask) - } + 2 -> { + onImageLabelingListener?.onSuccess(result) + } - //PoseDetection - val poseTask = handlePoseDetection(it) - if (poseTask != null) { - tasks.add(poseTask) - } + 3 -> { + onObjectDetectedListener?.onSuccess(result) + } - //ImageLabeling - val imageTask = handleImageLabeling(it) - if (imageTask != null) { - tasks.add(imageTask) - } + 4 -> { + onPoseDetectedListener?.onSuccess(result) + } - //ObjectDetection - val objectTask = handleObjectDetection(it) - if (objectTask != null) { - tasks.add(objectTask) - } + 5 -> { + onSelfieSegmentationListener?.onSuccess(result) + } - // TextRecognition - val textTask = handleTextRecognition(it) - if (textTask != null) { - tasks.add(textTask) - } + 6 -> { + onTextRecognitionListener?.onSuccess(result) + } - // SelfieSegmentation - val selfieTask = handleSelfieSegmentation(it) - if (selfieTask != null) { - tasks.add(selfieTask) - } + else -> {} + } + } + } catch (e: Exception) { + when (processor.type) { + 0 -> { + onBarcodeScanningListener?.onError( + e.message ?: "Failed to complete barcode scanning.", e + ) + } + + 1 -> { + onFacesDetectedListener?.onError( + e.message ?: "Failed to complete face detection.", e + ) + } + + 2 -> { + onImageLabelingListener?.onError( + e.message + ?: "Failed to complete image label detection.", e + ) + } - if (tasks.isNotEmpty()) { - val proxy = it - Tasks.whenAllComplete(tasks).addOnCompleteListener { + 3 -> { + onObjectDetectedListener?.onError( + e.message ?: "Failed to complete object detection.", e + ) + } + + 4 -> { + onPoseDetectedListener?.onError( + e.message ?: "Failed to complete pose detection.", e + ) + } + + 5 -> { + onSelfieSegmentationListener?.onError( + e.message + ?: "Failed to complete selfie segmentation detection.", + e + ) + } + + 6 -> { + onTextRecognitionListener?.onError( + e.message ?: "Failed to complete text recognition.", e + ) + } + } + } + } + } + + executor.execute { proxy.close() resetCurrentFrame() } + } else { + proxy.close() + resetCurrentFrame() } } } @@ -896,31 +725,50 @@ class Camera2 @JvmOverloads constructor( } } - val builder = ImageCapture.Builder().apply { + val builder = ImageCapture.Builder().apply { if (getDeviceRotation() > -1) { setTargetRotation(getDeviceRotation()) } if (pictureSize == "0x0") { - setTargetAspectRatio( - when (displayRatio) { - "16:9" -> AspectRatio.RATIO_16_9 - else -> AspectRatio.RATIO_4_3 - } + setResolutionSelector( + ResolutionSelector.Builder() + .apply { + setAspectRatioStrategy( + AspectRatioStrategy( + when (displayRatio) { + "16:9" -> AspectRatio.RATIO_16_9 + else -> AspectRatio.RATIO_4_3 + }, AspectRatioStrategy.FALLBACK_RULE_AUTO + ) + ) + } + .build() ) } else { - try { - setTargetResolution( - android.util.Size.parseSize(pictureSize) - ) - } catch (e: Exception) { - setTargetAspectRatio( - when (displayRatio) { - "16:9" -> AspectRatio.RATIO_16_9 - else -> AspectRatio.RATIO_4_3 + setResolutionSelector( + ResolutionSelector.Builder() + .apply { + try { + setResolutionStrategy( + ResolutionStrategy( + android.util.Size.parseSize(pictureSize), + ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER + ) + ) + } catch (e: Exception) { + setAspectRatioStrategy( + AspectRatioStrategy( + when (displayRatio) { + "16:9" -> AspectRatio.RATIO_16_9 + else -> AspectRatio.RATIO_4_3 + }, AspectRatioStrategy.FALLBACK_RULE_AUTO + ) + ) + } } - ) - } + .build() + ) } setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) setFlashMode(getFlashMode()) @@ -934,36 +782,43 @@ class Camera2 @JvmOverloads constructor( CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_AUTO ) } + WhiteBalance.Sunny -> { extender.setCaptureRequestOption( CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_DAYLIGHT ) } + WhiteBalance.Cloudy -> { extender.setCaptureRequestOption( CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_CLOUDY_DAYLIGHT ) } + WhiteBalance.Shadow -> { extender.setCaptureRequestOption( CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_SHADE ) } + WhiteBalance.Twilight -> { extender.setCaptureRequestOption( CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_TWILIGHT ) } + WhiteBalance.Fluorescent -> { extender.setCaptureRequestOption( CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_FLUORESCENT ) } + WhiteBalance.Incandescent -> { extender.setCaptureRequestOption( CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_INCANDESCENT ) } + WhiteBalance.WarmFluorescent -> { extender.setCaptureRequestOption( CaptureRequest.CONTROL_AWB_MODE, @@ -973,65 +828,177 @@ class Camera2 @JvmOverloads constructor( } // handle ultra wide af mode - if (camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 1.0f < 1.0f) { - extender.setCaptureRequestOption(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF) + if ((camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 1.0f) < 1.0f) { + extender.setCaptureRequestOption( + CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_OFF + ) } + imageCapture = builder.build() if (wasBounded || autoBound) { cameraProvider?.let { cameraProvider -> - if (cameraProvider.isBound(imageCapture!!)) { - cameraProvider.bindToLifecycle( - context as LifecycleOwner, - selectorFromPosition(), - imageCapture!!, - preview!! - ) - } + camera = cameraProvider.bindToLifecycle( + context as LifecycleOwner, + selectorFromPosition(), + imageCapture!!, + preview!! + ) } } } + + class CameraInfo(val id: String, characteristics: Camera2CameraInfo) { + + + val lensFacing by lazy { + when (characteristics.getCameraCharacteristic(CameraCharacteristics.LENS_FACING)!!) { + CameraCharacteristics.LENS_FACING_BACK -> 0 + CameraCharacteristics.LENS_FACING_FRONT -> 1 + CameraCharacteristics.LENS_FACING_EXTERNAL -> 2 + else -> 2 + } + } + + val maxDigitalZoom by lazy { + characteristics.getCameraCharacteristic(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) + ?: 1f + } + + val zoomRange by lazy { + val range = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + characteristics.getCameraCharacteristic(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE) + } else { + null + } + return@lazy range ?: Range(1f, maxDigitalZoom) + } + + val activeSize = + characteristics.getCameraCharacteristic(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)!! + + val minZoom by lazy { zoomRange.lower.toDouble() } + val maxZoom by lazy { zoomRange.upper.toDouble() } + + val hardwareLevel = + when (characteristics.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)) { + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY -> 0 + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED -> 1 + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL -> 1 + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL -> 2 + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 -> 3 + else -> 0 + } + + override fun toString(): String { + return "cameraId $id\n" + + "maxDigitalZoom $maxDigitalZoom\n" + + "zoomRange $zoomRange\n" + + "activeSize $activeSize\n" + + "minZoom $minZoom\n" + + "maxZoom $maxZoom\n" + + "hardwareLevel $hardwareLevel" + } + } + + // private var cameraInfoCache: MutableMap = mutableMapOf() private fun initPreview() { + val previewBuilder = Preview.Builder() .apply { - setTargetAspectRatio( - when (displayRatio) { - "16:9" -> AspectRatio.RATIO_16_9 - else -> AspectRatio.RATIO_4_3 - } + setResolutionSelector( + ResolutionSelector.Builder().apply { + setAspectRatioStrategy( + AspectRatioStrategy( + when (displayRatio) { + "16:9" -> AspectRatio.RATIO_16_9 + else -> AspectRatio.RATIO_4_3 + }, AspectRatioStrategy.FALLBACK_RULE_AUTO + ) + ) + }.build() ) + } + preview = previewBuilder .build() .also { it.setSurfaceProvider(this.previewView.surfaceProvider) } + if (imageAnalysis == null) { + setUpAnalysis() + } + + bindDefaultUseCase() + + if (defaultLens == CameraLens.auto){ + camera?.cameraInfo?.let { + val info = Camera2CameraInfo.from(it) + val cameraInfo = CameraInfo(info.cameraId, info) + + + val builder = Preview.Builder() + .apply { + setResolutionSelector( + ResolutionSelector.Builder().apply { + setAspectRatioStrategy( + AspectRatioStrategy( + when (displayRatio) { + "16:9" -> AspectRatio.RATIO_16_9 + else -> AspectRatio.RATIO_4_3 + }, AspectRatioStrategy.FALLBACK_RULE_AUTO + ) + ) + }.build() + ) + + } + + val extender = Camera2Interop.Extender(builder) + + val zoomRange = cameraInfo.zoomRange + val zoomClamped = zoomRange.clamp(zoom) + + + if (cameraInfo.hardwareLevel >= 1 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + extender.setCaptureRequestOption(CaptureRequest.CONTROL_ZOOM_RATIO, zoomClamped) + } else { + val size = cameraInfo.activeSize + + val dx = (size.width() / zoomClamped / 2).toInt() + val dy = (size.height() / zoomClamped / 2).toInt() + val left = size.centerX() - this.left - dx + val top = size.centerY() - this.top - dy + val right = size.centerX() - this.left + dx + val bottom = size.centerY() - this.top + dy + + val zoomed = Rect(left, top, right, bottom) + extender.setCaptureRequestOption(CaptureRequest.SCALER_CROP_REGION, zoomed) + } + + preview = previewBuilder + .build() + .also { + it.setSurfaceProvider(this.previewView.surfaceProvider) + } + + safeUnbindAll() + bindDefaultUseCase() - camera = if (detectorType != DetectorType.None && isMLSupported) { - if (imageAnalysis == null) { - setUpAnalysis() } - cameraProvider?.bindToLifecycle( - context as LifecycleOwner, - selectorFromPosition(), - preview, - imageAnalysis - ) - } else { - cameraProvider?.bindToLifecycle( - context as LifecycleOwner, - selectorFromPosition(), - preview - ) } - if(pendingAutoFocus) { + + if (pendingAutoFocus) { handleAutoFocus() } + listener?.onReady() } @@ -1060,7 +1027,7 @@ class Camera2 @JvmOverloads constructor( getRecorderQuality(quality), FallbackStrategy.lowerQualityOrHigherThan(androidx.camera.video.Quality.SD) ) - ).setExecutor(videoCaptureExecutor) + ) .build() @@ -1072,6 +1039,8 @@ class Camera2 @JvmOverloads constructor( } } + private var zoomRange = 1F..1F + @SuppressLint("RestrictedApi", "UnsafeOptInUsageError") private fun refreshCamera() { if (pause) { @@ -1090,9 +1059,7 @@ class Camera2 @JvmOverloads constructor( preview?.setSurfaceProvider(null) preview = null - if (detectorType != DetectorType.None) { - setUpAnalysis() - } + setUpAnalysis() initPreview() @@ -1100,6 +1067,7 @@ class Camera2 @JvmOverloads constructor( handleZoom() + camera?.cameraInfo?.let { val streamMap = Camera2CameraInfo.from(it) .getCameraCharacteristic(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) @@ -1132,7 +1100,7 @@ class Camera2 @JvmOverloads constructor( } } - updateImageCapture(true) + updateImageCapture(false) if (flashMode == CameraFlashMode.TORCH && camera?.cameraInfo?.hasFlashUnit() == true) { camera?.cameraControl?.enableTorch(true) @@ -1165,8 +1133,10 @@ class Camera2 @JvmOverloads constructor( it.cameraControl.enableTorch(false) imageCapture?.flashMode = ImageCapture.FLASH_MODE_OFF } + CameraFlashMode.ON, CameraFlashMode.RED_EYE -> imageCapture?.flashMode = ImageCapture.FLASH_MODE_ON + CameraFlashMode.AUTO -> imageCapture?.flashMode = ImageCapture.FLASH_MODE_AUTO CameraFlashMode.TORCH -> it.cameraControl.enableTorch(true) } @@ -1216,17 +1186,11 @@ class Camera2 @JvmOverloads constructor( initVideoCapture() } cameraProvider?.let { - if (it.isBound(imageCapture!!)) { - it.unbind(imageCapture!!) - } - - if (!it.isBound(videoCapture!!)) { - it.bindToLifecycle( - context as LifecycleOwner, - selectorFromPosition(), - videoCapture!! - ) - } + camera = it.bindToLifecycle( + context as LifecycleOwner, + selectorFromPosition(), + videoCapture!! + ) } val opts = FileOutputOptions.Builder(file!!).build() @@ -1251,6 +1215,7 @@ class Camera2 @JvmOverloads constructor( startDurationTimer() listener?.onCameraVideoStart() } + is VideoRecordEvent.Finalize -> { isRecording = false stopDurationTimer() @@ -1271,6 +1236,8 @@ class Camera2 @JvmOverloads constructor( synchronized(mLock) { isForceStopping = false } + } else { + bindDefaultUseCase() } } else { if (isForceStopping) { @@ -1284,6 +1251,7 @@ class Camera2 @JvmOverloads constructor( isForceStopping = false } } else { + bindDefaultUseCase() if (saveToGallery && hasStoragePermission()) { val values = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) @@ -1350,14 +1318,7 @@ class Camera2 @JvmOverloads constructor( if (file != null) { file!!.delete() } - cameraProvider?.let { - if (it.isBound(videoCapture!!)) { - it.unbind(videoCapture!!) - } - if (it.isBound(imageCapture!!)) { - it.unbind(imageCapture!!) - } - } + bindDefaultUseCase() isForceStopping = false listener?.onCameraError("Failed to record video.", e) } @@ -1394,11 +1355,10 @@ class Camera2 @JvmOverloads constructor( File(context.getExternalFilesDir(null), fileName) } - cameraProvider?.let { provider -> - videoCapture?.let { if (provider.isBound(it)) provider.unbind(it) } + cameraProvider?.let { provider -> if (imageCapture == null) { - updateImageCapture(true) + updateImageCapture(false) } imageCapture?.let { capture -> if (!provider.isBound(capture)) { @@ -1421,7 +1381,7 @@ class Camera2 @JvmOverloads constructor( val useImageProxy = autoSquareCrop || !allowExifRotation if (useImageProxy) { imageCapture?.takePicture( - imageCaptureExecutor, + executor, object : ImageCapture.OnImageCapturedCallback() { override fun onCaptureSuccess(image: ImageProxy) { processImageProxy(image, fileName) @@ -1439,7 +1399,7 @@ class Camera2 @JvmOverloads constructor( options.setMetadata(meta) imageCapture?.takePicture( options.build(), - imageCaptureExecutor, + executor, object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { processImageFile(fileName) // outputFileResults.savedUri.toString() is null @@ -1447,6 +1407,15 @@ class Camera2 @JvmOverloads constructor( override fun onError(exception: ImageCaptureException) { listener?.onCameraError("Failed to take photo image", exception) + ContextCompat.getMainExecutor(context).execute { + cameraProvider?.unbind(imageCapture) + cameraProvider?.bindToLifecycle( + context as LifecycleOwner, + selectorFromPosition(), + preview, + imageAnalysis + ) + } } }) } @@ -1556,6 +1525,9 @@ class Camera2 @JvmOverloads constructor( } catch (_: Exception) { } + + + bindDefaultUseCase() if (!isError) { if (saveToGallery && hasStoragePermission()) { val values = ContentValues().apply { @@ -1600,8 +1572,9 @@ class Camera2 @JvmOverloads constructor( } else { listener?.onCameraPhoto(file) } - } + + } } diff --git a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/CameraBase.kt b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/CameraBase.kt index 50f6ba4..fd0a95f 100644 --- a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/CameraBase.kt +++ b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/CameraBase.kt @@ -15,11 +15,12 @@ import android.view.OrientationEventListener import android.widget.FrameLayout import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.databinding.ObservableArrayList +import androidx.databinding.ObservableList import java.io.IOException import java.text.ParseException import java.text.SimpleDateFormat import java.util.* -import java.util.concurrent.Executors abstract class CameraBase @JvmOverloads constructor( @@ -49,9 +50,10 @@ abstract class CameraBase @JvmOverloads constructor( abstract val amplitudeEMA: Double abstract var isAudioLevelsEnabled: Boolean abstract val numberOfCameras: Int - abstract var detectorType: DetectorType var overridePhotoWidth: Int = -1 var overridePhotoHeight: Int = -1 + abstract val imageProcessors: ObservableList> + abstract var defaultLens: CameraLens abstract fun stop() abstract fun release() abstract fun startPreview() @@ -145,106 +147,6 @@ abstract class CameraBase @JvmOverloads constructor( abstract val previewSurface: Any internal val mainHandler = Handler(Looper.getMainLooper()) - internal var analysisExecutor = Executors.newCachedThreadPool() - - internal var barcodeScannerOptions: Any? = null - fun setBarcodeScannerOptions(value: Any) { - if (!isBarcodeScanningSupported) return - if (barcodeScannerOptions != null) { - val BarcodeScannerOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.barcodescanning.BarcodeScanner\$Options") - if (!BarcodeScannerOptionsClazz.isInstance(value)) return - barcodeScannerOptions = value - } - } - - - internal var faceDetectionOptions: Any? = null - fun setFaceDetectionOptions(value: Any) { - if (!isFaceDetectionSupported) return - if (faceDetectionOptions != null) { - val FaceDetectionOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.facedetection.FaceDetection\$Options") - if (!FaceDetectionOptionsClazz.isInstance(value)) return - faceDetectionOptions = value - } - } - - internal var imageLabelingOptions: Any? = null - fun setImageLabelingOptions(value: Any) { - if (!isImageLabelingSupported) return - if (imageLabelingOptions != null) { - val ImageLabelingOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.imagelabeling.ImageLabeling\$Options") - if (!ImageLabelingOptionsClazz.isInstance(value)) return - imageLabelingOptions = value - } - } - - internal var objectDetectionOptions: Any? = null - fun setObjectDetectionOptions(value: Any) { - if (!isObjectDetectionSupported) return - if (objectDetectionOptions != null) { - val ObjectDetectionOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.objectdetection.ObjectDetection\$Options") - if (!ObjectDetectionOptionsClazz.isInstance(value)) return - objectDetectionOptions = value - } - } - - internal var selfieSegmentationOptions: Any? = null - fun setSelfieSegmentationOptions(value: Any) { - if (!isSelfieSegmentationSupported) return - if (selfieSegmentationOptions != null) { - val SelfieSegmentationOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.selfiesegmentation.SelfieSegmentation\$Options") - if (!SelfieSegmentationOptionsClazz.isInstance(value)) return - selfieSegmentationOptions = value - } - } - - internal fun initOptions() { - if (isBarcodeScanningSupported) { - Class.forName("io.github.triniwiz.fancycamera.barcodescanning.BarcodeScanner") - val BarcodeScannerOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.barcodescanning.BarcodeScanner\$Options") - barcodeScannerOptions = BarcodeScannerOptionsClazz.newInstance() - } - - - if (isFaceDetectionSupported) { - val FaceDetectionOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.facedetection.FaceDetection\$Options") - faceDetectionOptions = FaceDetectionOptionsClazz.newInstance() - } - - if (isImageLabelingSupported) { - val ImageLabelingOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.imagelabeling.ImageLabeling\$Options") - imageLabelingOptions = ImageLabelingOptionsClazz.newInstance() - } - - if (isPoseDetectionSupported) { - // noop - } - - - if (isObjectDetectionSupported) { - val ObjectDetectionOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.objectdetection.ObjectDetection\$Options") - objectDetectionOptions = ObjectDetectionOptionsClazz.newInstance() - } - - if (isSelfieSegmentationSupported) { - val SelfieSegmentationOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.selfiesegmentation.SelfieSegmentation\$Options") - selfieSegmentationOptions = SelfieSegmentationOptionsClazz.newInstance() - } - - if (isTextRecognitionSupported) { - // noop - } - } /** Device orientation in degrees 0-359 */ @@ -267,7 +169,11 @@ abstract class CameraBase @JvmOverloads constructor( return mDuration } - internal var recorder: MediaRecorder = MediaRecorder() + internal var recorder: MediaRecorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + MediaRecorder(context) + } else { + MediaRecorder() + } internal var listener: CameraEventListener? = null @@ -338,8 +244,8 @@ abstract class CameraBase @JvmOverloads constructor( recorder.start() isGettingAudioLevels = true mEMA = 0.0 - } catch (e: IOException) { - } catch (e: Exception) { + } catch (_: IOException) { + } catch (_: Exception) { } } @@ -486,74 +392,5 @@ abstract class CameraBase @JvmOverloads constructor( companion object { internal val EMA_FILTER = 0.6 - internal var isBarcodeScanningSupported = false - internal var isFaceDetectionSupported = false - internal var isImageLabelingSupported = false - internal var isObjectDetectionSupported = false - internal var isPoseDetectionSupported = false - internal var isTextRecognitionSupported = false - internal var isSelfieSegmentationSupported = false - internal val isMLSupported: Boolean - get() { - return isFaceDetectionSupported || isTextRecognitionSupported || isBarcodeScanningSupported || - isPoseDetectionSupported || isImageLabelingSupported || isObjectDetectionSupported - } - - internal fun detectSupport() { - isBarcodeScanningSupported = try { - Class.forName("io.github.triniwiz.fancycamera.barcodescanning.BarcodeScanner") - true - } catch (e: ClassNotFoundException) { - false - } - - isFaceDetectionSupported = try { - Class.forName("io.github.triniwiz.fancycamera.facedetection.FaceDetection") - true - } catch (e: ClassNotFoundException) { - false - } - - isImageLabelingSupported = try { - Class.forName("io.github.triniwiz.fancycamera.imagelabeling.ImageLabeling") - true - } catch (e: ClassNotFoundException) { - false - } - - isPoseDetectionSupported = try { - Class.forName("io.github.triniwiz.fancycamera.posedetection.PoseDetection") - true - } catch (e: ClassNotFoundException) { - false - } - - isObjectDetectionSupported = try { - Class.forName("io.github.triniwiz.fancycamera.objectdetection.ObjectDetection") - true - } catch (e: ClassNotFoundException) { - false - } - - isSelfieSegmentationSupported = try { - Class.forName("io.github.triniwiz.fancycamera.selfiesegmentation.SelfieSegmentation") - true - } catch (e: ClassNotFoundException) { - false - } - - - isTextRecognitionSupported = try { - Class.forName("io.github.triniwiz.fancycamera.textrecognition.TextRecognition") - true - } catch (e: ClassNotFoundException) { - false - } - - } - - init { - detectSupport() - } } } \ No newline at end of file diff --git a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/CameraLens.kt b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/CameraLens.kt new file mode 100644 index 0000000..8c7f2a0 --- /dev/null +++ b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/CameraLens.kt @@ -0,0 +1,14 @@ +package io.github.triniwiz.fancycamera + +enum class CameraLens { + auto, + + /// Telephoto Lens + telephoto, + + /// Wide Lens + wide, + + /// Ultra Lens + ultrawide +} \ No newline at end of file diff --git a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/CameraPosition.kt b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/CameraPosition.kt index 870171b..ad13bad 100644 --- a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/CameraPosition.kt +++ b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/CameraPosition.kt @@ -1,9 +1,22 @@ package io.github.triniwiz.fancycamera +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.camera.core.CameraSelector + enum class CameraPosition constructor(val value: Int) { BACK(0), FRONT(1); + @get:RequiresApi(Build.VERSION_CODES.LOLLIPOP) + val lenFacing: Int + get() { + return when (this) { + FRONT -> CameraSelector.LENS_FACING_FRONT + else -> CameraSelector.LENS_FACING_BACK + } + } + companion object { fun from(value: Int): CameraPosition = values().first { it.value == value } } diff --git a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/FancyCamera.kt b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/FancyCamera.kt index 22ed843..00e3509 100644 --- a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/FancyCamera.kt +++ b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/FancyCamera.kt @@ -17,6 +17,8 @@ import android.util.AttributeSet import android.util.SparseIntArray import android.view.Surface import android.widget.FrameLayout +import androidx.databinding.ObservableList +import java.util.ArrayList @SuppressLint("RestrictedApi") @@ -33,6 +35,54 @@ class FancyCamera : FrameLayout { private var mEMA = 0.0 private lateinit var cameraView: CameraBase + val imageProcessors: List> + get() { + return cameraView.imageProcessors + } + + fun addImageProcessor(processor: ImageProcessor<*>) { + cameraView.imageProcessors.add(processor) + } + + fun addImageProcessors(processors: Array>) { + cameraView.imageProcessors.addAll(processors) + } + + fun removeImageProcessor(processor: ImageProcessor<*>) { + cameraView.imageProcessors.remove(processor) + } + + fun removeImageProcessors(processors: Array>) { + cameraView.imageProcessors.removeAll(processors.toSet()) + } + + fun clearImageProcessor() { + cameraView.imageProcessors.clear() + } + + fun removeImageProcessor(type: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + cameraView.imageProcessors.removeIf { + it.type == type + } + } else { + val iterator = cameraView.imageProcessors.iterator() + while (iterator.hasNext()) { + val processor = iterator.next() + if (processor.type == type) { + iterator.remove() + } + } + } + } + + var defaultLens: CameraLens + get() { + return cameraView.defaultLens + } + set(value) { + cameraView.defaultLens = value + } var enableAudio: Boolean get() { return cameraView.enableAudio @@ -94,26 +144,7 @@ class FancyCamera : FrameLayout { set(value) { cameraView.whiteBalance = value } - var detectorType: DetectorType - get() { - return cameraView.detectorType - } - set(value) { - cameraView.detectorType = value - } - - - fun setDetectorWithName(name: String?) { - if (name == null) { - detectorType = DetectorType.None - return - } - - DetectorType.fromName(name)?.let { name -> - detectorType = name - } - } var ratio: String get() { @@ -285,57 +316,6 @@ class FancyCamera : FrameLayout { cameraView.setOnSelfieSegmentationListener(callback) } - var barcodeScannerOptions: Any? - get() { - return cameraView.barcodeScannerOptions - } - set(value) { - if (CameraBase.isBarcodeScanningSupported) { - cameraView.barcodeScannerOptions = value - } - } - - var faceDetectionOptions: Any? - get() { - return cameraView.faceDetectionOptions - } - set(value) { - if (CameraBase.isFaceDetectionSupported) { - cameraView.faceDetectionOptions = value - } - } - - var imageLabelingOptions: Any? - get() { - return cameraView.imageLabelingOptions - } - set(value) { - if (CameraBase.isImageLabelingSupported) { - cameraView.imageLabelingOptions = value - } - } - - var objectDetectionOptions: Any? - get() { - return cameraView.objectDetectionOptions - } - set(value) { - if (CameraBase.isObjectDetectionSupported) { - cameraView.objectDetectionOptions = value - } - } - - - var selfieSegmentationOptions: Any? - get() { - return cameraView.selfieSegmentationOptions - } - set(value) { - if (CameraBase.isSelfieSegmentationSupported) { - cameraView.selfieSegmentationOptions = value - } - } - var overridePhotoWidth: Int set(value) { diff --git a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/ImageProcessor.kt b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/ImageProcessor.kt new file mode 100644 index 0000000..5c6a50b --- /dev/null +++ b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/ImageProcessor.kt @@ -0,0 +1,35 @@ +package io.github.triniwiz.fancycamera + +import android.graphics.Bitmap +import android.media.Image +import com.google.mlkit.vision.common.InputImage +import java.util.concurrent.Future +import java.util.concurrent.FutureTask + +interface ImageProcessor { + val type: Int + get() = -1 + + fun process(image: InputImage): FutureTask + + fun process( + bytes: ByteArray, + width: Int, + height: Int, + rotation: Int, + format: Int + ): FutureTask { + val input = InputImage.fromByteArray(bytes, width, height, rotation, format) + return process(input) + } + + fun process(image: Image, rotation: Int): FutureTask { + return process(InputImage.fromMediaImage(image, rotation)) + } + + fun process(bitmap: Bitmap, rotation: Int): FutureTask { + val input = InputImage.fromBitmap(bitmap, rotation) + return process(input) + } + +} \ No newline at end of file diff --git a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/ML.kt b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/ML.kt index 5107a3d..66523e4 100644 --- a/fancycamera/src/main/java/io/github/triniwiz/fancycamera/ML.kt +++ b/fancycamera/src/main/java/io/github/triniwiz/fancycamera/ML.kt @@ -7,616 +7,145 @@ import android.os.Looper import com.google.android.gms.tasks.Task import com.google.android.gms.tasks.TaskCompletionSource import com.google.android.gms.tasks.Tasks +import com.google.mlkit.vision.common.InputImage import org.json.JSONObject +import java.util.ArrayList import java.util.concurrent.ExecutorService import java.util.concurrent.Executors class ML { companion object { - private var mainHandler = Handler(Looper.getMainLooper()) private var executor: ExecutorService = Executors.newCachedThreadPool() @JvmStatic - fun processBytes( + fun process( byteArray: ByteArray, width: Int, height: Int, rotation: Int, format: Int, - options: String, + imageProcessors: List>, callback: ImageAnalysisCallback ) { - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val fromMediaImage = - InputImageClazz.getMethod( - "fromByteArray", - ByteArray::class.java, - Int::class.java, - Int::class.java, - Int::class.java, - Int::class.java - ) - process( - fromMediaImage.invoke(null, byteArray, width, height, rotation, format), - options, callback - ) + val image = InputImage.fromByteArray(byteArray, width, height, rotation, format) + process(image, imageProcessors, callback) } @JvmStatic fun processImage( image: Bitmap, rotation: Int, - options: String, + imageProcessors: List>, callback: ImageAnalysisCallback ) { - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val fromMediaImage = - InputImageClazz.getMethod( - "fromBitmap", - Bitmap::class.java, - Int::class.java - ) - process( - fromMediaImage.invoke(null, image, rotation), - options, callback - ) + val input = InputImage.fromBitmap(image, rotation) + process(input, imageProcessors, callback) } private fun process( - inputImage: Any?, - options: String, + inputImage: InputImage?, + imageProcessors: List>, callback: ImageAnalysisCallback ) { - val json = try { - JSONObject(options) - } catch (e: Exception) { - callback.onError(e.message ?: "", e) - null - } ?: return + executor.execute { inputImage?.let { image -> - val tasks = mutableListOf>() - val detectorType = (json["detectorType"] as Int?)?.let { type -> - DetectorType.fromInt(type) - } ?: DetectorType.None - - val results = mutableListOf>() - - //BarcodeScanning - if (CameraBase.isFaceDetectionSupported && (detectorType == DetectorType.Barcode || detectorType == DetectorType.All)) { - json.opt("barcodeScanning")?.let { - try { - val OptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.barcodescanning.BarcodeScanner\$Options") - val fromJson = - OptionsClazz.getMethod( - "fromJson", - JSONObject::class.java, - Boolean::class.java - ) - - val opts = fromJson.invoke(null, it, true) - val barcodeTask = - handleBarcodeScanning(image, opts!!, object : - ImageAnalysisCallback { - override fun onSuccess(result: Any) { - results.add(arrayOf("barcode", result)) - } + val ret = mutableListOf>() + var hasError = false + for (processor in imageProcessors) { + val process = processor.process(image) + try { + val result = process.get() + when (processor.type) { + 0 -> { + ret.add(arrayOf("barcode", result)) + } - override fun onError( - message: String, - exception: java.lang.Exception - ) { - } - }) - if (barcodeTask != null) { - tasks.add(barcodeTask) + 1 -> { + ret.add(arrayOf("face", result)) } - } catch (e: Exception) { - e.printStackTrace() - } - } - } - // FaceDetection - if (CameraBase.isFaceDetectionSupported && (detectorType == DetectorType.Face || detectorType == DetectorType.All)) { - json.opt("faceDetection")?.let { - try { - val OptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.facedetection.FaceDetection\$Options") - val fromJson = - OptionsClazz.getMethod( - "fromJson", - JSONObject::class.java, - Boolean::class.java - ) + 2 -> { + ret.add(arrayOf("image", result)) + } - val opts = fromJson.invoke(null, it, true) - val faceTask = handleFaceDetection(image, opts!!, object : - ImageAnalysisCallback { - override fun onSuccess(result: Any) { - results.add(arrayOf("face", result)) - } + 3 -> { + ret.add(arrayOf("object", result)) + } - override fun onError( - message: String, - exception: java.lang.Exception - ) { - } - }) - if (faceTask != null) { - tasks.add(faceTask) + 4 -> { + ret.add(arrayOf("pose", result)) } - } catch (e: Exception) { - } - } - } - //PoseDetection - if (CameraBase.isPoseDetectionSupported || (detectorType == DetectorType.Pose || detectorType == DetectorType.All)) { - try { - val poseTask = handlePoseDetection(image, object : - ImageAnalysisCallback { - override fun onSuccess(result: Any) { - results.add(arrayOf("pose", result)) + 5 -> { + ret.add(arrayOf("selfie", result)) } - override fun onError( - message: String, - exception: java.lang.Exception - ) { + 6 -> { + ret.add(arrayOf("text", result)) } - }) - if (poseTask != null) { - tasks.add(poseTask) } } catch (e: Exception) { - } - } - - //ImageLabeling - if (CameraBase.isImageLabelingSupported && (detectorType == DetectorType.Image || detectorType == DetectorType.All)) { - json.opt("imageLabeling")?.let { - try { - val OptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.imagelabeling.ImageLabeling\$Options") - val fromJson = - OptionsClazz.getMethod( - "fromJson", - JSONObject::class.java, - Boolean::class.java + when (processor.type) { + 0 -> { + hasError = true + callback.onError( + e.message ?: "Failed to complete barcode scanning.", e ) - - val opts = fromJson.invoke(null, it, true) - val imageTask = handleImageLabeling(image, opts!!, object : - ImageAnalysisCallback { - override fun onSuccess(result: Any) { - results.add(arrayOf("image", result)) - } - - override fun onError( - message: String, - exception: java.lang.Exception - ) { - } - }) - if (imageTask != null) { - tasks.add(imageTask) } - } catch (e: Exception) { - } - } - } - //ObjectDetection - if (CameraBase.isObjectDetectionSupported && (detectorType == DetectorType.Object || detectorType == DetectorType.All)) { - json.opt("objectDetection")?.let { - val OptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.objectdetection.ObjectDetection\$Options") - val fromJson = - OptionsClazz.getMethod( - "fromJson", - JSONObject::class.java, - Boolean::class.java - ) - - val opts = fromJson.invoke(null, it, true) - val objectTask = handleObjectDetection(image, opts!!, object : - ImageAnalysisCallback { - override fun onSuccess(result: Any) { - results.add(arrayOf("object", result)) + 1 -> { + hasError = true + callback.onError( + e.message ?: "Failed to complete face detection.", e + ) } - override fun onError( - message: String, - exception: java.lang.Exception - ) { + 2 -> { + hasError = true + callback.onError( + e.message + ?: "Failed to complete image label detection.", e + ) } - }) - if (objectTask != null) { - tasks.add(objectTask) - } - } - } - // TextRecognition - if (CameraBase.isTextRecognitionSupported || (detectorType == DetectorType.Text || detectorType == DetectorType.All)) { - try { - val textTask = handleTextRecognition(image, object : - ImageAnalysisCallback { - override fun onSuccess(result: Any) { - results.add(arrayOf("text", result)) + 3 -> { + hasError = true + callback.onError( + e.message ?: "Failed to complete object detection.", e + ) } - override fun onError( - message: String, - exception: java.lang.Exception - ) { + 4 -> { + hasError = true + callback.onError( + e.message ?: "Failed to complete pose detection.", e + ) } - }) - if (textTask != null) { - tasks.add(textTask) - } - } catch (e: Exception) { - } - } - // SelfieSegmentation - if (CameraBase.isSelfieSegmentationSupported || (detectorType == DetectorType.Selfie || detectorType == DetectorType.All)) { - json.opt("selfieSegmentation")?.let { - try { - val OptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.selfiesegmentation.SelfieSegmentation\$Options") - val fromJson = - OptionsClazz.getMethod( - "fromJson", - JSONObject::class.java, - Boolean::class.java + 5 -> { + hasError = true + callback.onError( + e.message + ?: "Failed to complete selfie segmentation detection.", + e ) + } - val opts = fromJson.invoke(null, it, true) - val selfieTask = handleSelfieSegmentation(image, opts!!, object : - ImageAnalysisCallback { - override fun onSuccess(result: Any) { - results.add(arrayOf("selfie", result)) - } - - override fun onError( - message: String, - exception: java.lang.Exception - ) { - } - }) - if (selfieTask != null) { - tasks.add(selfieTask) + 6 -> { + hasError = true + callback.onError( + e.message ?: "Failed to complete text recognition.", e + ) } - } catch (e: Exception) { + } + if (hasError) { + break } } } - - if (tasks.isNotEmpty()) { - Tasks.whenAllComplete(tasks).addOnCompleteListener { - callback.onSuccess(results) - } - } - } - } - } - - - @SuppressLint("UnsafeOptInUsageError") - private fun handleBarcodeScanning( - inputImage: Any, - options: Any, - callback: ImageAnalysisCallback - ): Task? { - if (!CameraBase.isBarcodeScanningSupported) { - return null - } - - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val BarcodeScannerClazz = - Class.forName("io.github.triniwiz.fancycamera.barcodescanning.BarcodeScanner") - val barcodeScanner = BarcodeScannerClazz.newInstance() - - val BarcodeScannerOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.barcodescanning.BarcodeScanner\$Options") - - - val processImageMethod = BarcodeScannerClazz.getDeclaredMethod( - "processImage", - InputImageClazz, - BarcodeScannerOptionsClazz - ) - - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke( - barcodeScanner, - inputImage, - options - ) as Task - task.addOnSuccessListener(executor, { - if (it.isNotEmpty()) { - mainHandler.post { - callback.onSuccess(it) - } - } - }).addOnFailureListener(executor, { - mainHandler.post { - callback.onError( - it.message - ?: "Failed to complete face detection.", it - ) - } - }).addOnCompleteListener(executor, { - returnTask.setResult(true) - }) - return returnTask.task - } - - @SuppressLint("UnsafeOptInUsageError") - private fun handleFaceDetection( - inputImage: Any, - options: Any, - callback: ImageAnalysisCallback - ): Task? { - if (!CameraBase.isFaceDetectionSupported) { - return null - } - - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val FaceDetectionClazz = - Class.forName("io.github.triniwiz.fancycamera.facedetection.FaceDetection") - val faceDetection = FaceDetectionClazz.newInstance() - - val FaceDetectionOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.facedetection.FaceDetection\$Options") - - val processImageMethod = FaceDetectionClazz.getDeclaredMethod( - "processImage", - InputImageClazz, - FaceDetectionOptionsClazz - ) - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke( - faceDetection, - inputImage, - options - ) as Task - task.addOnSuccessListener(executor, { - if (it.isNotEmpty()) { - mainHandler.post { - callback.onSuccess(it) - } - } - }).addOnFailureListener(executor, { - mainHandler.post { - callback.onError( - it.message - ?: "Failed to complete face detection.", it - ) - } - }).addOnCompleteListener(executor, { - returnTask.setResult(true) - }) - return returnTask.task - } - - @SuppressLint("UnsafeOptInUsageError") - private fun handleImageLabeling( - inputImage: Any, - options: Any, - callback: ImageAnalysisCallback - ): Task? { - if (!CameraBase.isImageLabelingSupported) { - return null - } - - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - - val ImageLabelingClazz = - Class.forName("io.github.triniwiz.fancycamera.imagelabeling.ImageLabeling") - val imageLabeling = ImageLabelingClazz.newInstance() - val ImageLabelingOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.imagelabeling.ImageLabeling\$Options") - val processImageMethod = ImageLabelingClazz.getDeclaredMethod( - "processImage", - InputImageClazz, - ImageLabelingOptionsClazz - ) - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke( - imageLabeling, - inputImage, - options - ) as Task - task.addOnSuccessListener(executor, { - if (it.isNotEmpty()) { - mainHandler.post { - callback.onSuccess(it) - } - } - }).addOnFailureListener(executor, { - mainHandler.post { - callback.onError( - it.message - ?: "Failed to complete face detection.", it - ) - } - }).addOnCompleteListener(executor, { - returnTask.setResult(true) - }) - return returnTask.task - } - - @SuppressLint("UnsafeOptInUsageError") - private fun handleObjectDetection( - inputImage: Any, - options: Any, - callback: ImageAnalysisCallback - ): Task? { - if (!CameraBase.isObjectDetectionSupported) { - return null - } - - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val ObjectDetectionClazz = - Class.forName("io.github.triniwiz.fancycamera.objectdetection.ObjectDetection") - val objectDetection = ObjectDetectionClazz.newInstance() - val ObjectDetectionOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.objectdetection.ObjectDetection\$Options") - val processImageMethod = ObjectDetectionClazz.getDeclaredMethod( - "processImage", - InputImageClazz, - ObjectDetectionOptionsClazz - ) - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke( - objectDetection, - inputImage, - options - ) as Task - task.addOnSuccessListener(executor, { - if (it.isNotEmpty()) { - mainHandler.post { - callback.onSuccess(it) - } - } - }).addOnFailureListener(executor, { - mainHandler.post { - callback.onError( - it.message - ?: "Failed to complete face detection.", it - ) - } - }).addOnCompleteListener(executor, { - returnTask.setResult(true) - }) - return returnTask.task - } - - @SuppressLint("UnsafeOptInUsageError") - private fun handlePoseDetection( - inputImage: Any, - callback: ImageAnalysisCallback - ): Task? { - if (!CameraBase.isPoseDetectionSupported) { - return null - } - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val PoseDetectionClazz = - Class.forName("io.github.triniwiz.fancycamera.posedetection.PoseDetection") - val poseDetection = PoseDetectionClazz.newInstance() - val singleMode = PoseDetectionClazz.getDeclaredField("singleMode") - singleMode.setBoolean(poseDetection, true) - val processImageMethod = - PoseDetectionClazz.getDeclaredMethod("processImage", InputImageClazz) - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke(poseDetection, inputImage) as Task - task.addOnSuccessListener(executor, { - if (it.isNotEmpty()) { - mainHandler.post { - callback.onSuccess(it) - } - } - }).addOnFailureListener(executor, { - mainHandler.post { - callback.onError( - it.message - ?: "Failed to complete text recognition.", it - ) - } - }).addOnCompleteListener(executor, { - returnTask.setResult(true) - }) - return returnTask.task - } - - @SuppressLint("UnsafeOptInUsageError") - private fun handleTextRecognition( - inputImage: Any, - callback: ImageAnalysisCallback - ): Task? { - if (!CameraBase.isTextRecognitionSupported) { - return null - } - - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val TextRecognitionClazz = - Class.forName("io.github.triniwiz.fancycamera.textrecognition.TextRecognition") - val textRecognition = TextRecognitionClazz.newInstance() - val processImageMethod = - TextRecognitionClazz.getDeclaredMethod("processImage", InputImageClazz) - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke(textRecognition, inputImage) as Task - task.addOnSuccessListener(executor, { - if (it.isNotEmpty()) { - mainHandler.post { - callback.onSuccess(it) - } } - }).addOnFailureListener(executor, { - mainHandler.post { - callback.onError( - it.message - ?: "Failed to complete text recognition.", it - ) - } - }).addOnCompleteListener(executor, { - returnTask.setResult(true) - }) - return returnTask.task - } - - @SuppressLint("UnsafeOptInUsageError") - private fun handleSelfieSegmentation( - inputImage: Any, - options: Any, - callback: ImageAnalysisCallback - ): Task? { - if (!CameraBase.isSelfieSegmentationSupported) { - return null } - - val InputImageClazz = Class.forName("com.google.mlkit.vision.common.InputImage") - val SelfieSegmentationClazz = - Class.forName("io.github.triniwiz.fancycamera.selfiesegmentation.SelfieSegmentation") - val selfieSegmentation = SelfieSegmentationClazz.newInstance() - - val SelfieSegmentationOptionsClazz = - Class.forName("io.github.triniwiz.fancycamera.selfiesegmentation.SelfieSegmentation\$Options") - - val processImageMethod = SelfieSegmentationClazz.getDeclaredMethod( - "processImage", - InputImageClazz, - SelfieSegmentationOptionsClazz - ) - - val returnTask = TaskCompletionSource() - val task = processImageMethod.invoke( - selfieSegmentation, - inputImage, - options - ) as Task - task.addOnSuccessListener(executor, { - if (it != null) { - mainHandler.post { - callback.onSuccess(it) - } - } - }).addOnFailureListener(executor, { - mainHandler.post { - callback.onError( - it.message - ?: "Failed to complete text recognition.", it - ) - } - }).addOnCompleteListener(executor, { - returnTask.setResult(true) - }) - return returnTask.task } - } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 17d1f02..d070ad5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,13 +15,37 @@ org.gradle.jvmargs=-Xmx1536m # 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 -BUILD_TOOLS_VERSION=33.0.0 -COMPILE_SDK_VERSION=33 +BUILD_TOOLS_VERSION=34 +COMPILE_SDK_VERSION=34 android.useAndroidX=true android.enableJetifier=true -camerax_version = 1.2.1 -camerax_view_version = 1.2.1 -camerax_ext_version = 1.2.1 -camerax_lifecycle_version = 1.2.1 +camerax_version = 1.4.0-alpha05 +camerax_view_version = 1.4.0-alpha05 +camerax_ext_version = 1.4.0-alpha05 +camerax_lifecycle_version = 1.4.0-alpha05 gson_version = 2.10.1 -app_compat_version = 1.5.1 \ No newline at end of file +app_compat_version = 1.6.1 + + + +## Grouping +GROUP=io.github.triniwiz + +## Metadata +POM_DESCRIPTION=Fancy Android Camera. +POM_DEVELOPER_EMAIL=fortune.osei@yahoo.com +POM_DEVELOPER_ID=triniwiz +POM_DEVELOPER_NAME=Osei Fortune +POM_SCM_CONNECTION=scm\:git@github.com\:triniwiz/fancycamera.git +POM_SCM_DEV_CONNECTION=scm\:git@github.com\:triniwiz/fancycamera.git +POM_SCM_URL=https\://github.com/triniwiz/fancycamera +POM_URL=https\://github.com/triniwiz/fancycamera +POM_INCEPTION_YEAR=2019 + +VERSION_MAJOR=4 +VERSION_MINOR=0 +VERSION_PATCH=0 +VERSION_NAME=4.0.0-ALPHA.0 +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index de7c2d3..8fd1235 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Nov 23 20:21:06 AST 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/imageLabeling/build.gradle b/imageLabeling/build.gradle index 3ce0e15..4eb8e7c 100644 --- a/imageLabeling/build.gradle +++ b/imageLabeling/build.gradle @@ -4,12 +4,13 @@ plugins { } android { - compileSdkVersion COMPILE_SDK_VERSION as int + compileSdk = COMPILE_SDK_VERSION as int buildToolsVersion BUILD_TOOLS_VERSION as String defaultConfig { minSdkVersion 17 targetSdkVersion 33 + versionName = VERSION_NAME as String testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -22,22 +23,23 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } + namespace 'io.github.triniwiz.fancycamera.imagelabeling' } dependencies { + implementation project(":fancycamera") implementation "com.google.code.gson:gson:$gson_version" - implementation 'com.google.mlkit:image-labeling:17.0.7' - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'com.google.mlkit:image-labeling:17.0.8' implementation "androidx.appcompat:appcompat:$app_compat_version" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } -apply from: 'publish.gradle' \ No newline at end of file +apply from: "${rootProject.projectDir}/scripts/upload.gradle" \ No newline at end of file diff --git a/imageLabeling/gradle.properties b/imageLabeling/gradle.properties index b5b3540..af705c7 100644 --- a/imageLabeling/gradle.properties +++ b/imageLabeling/gradle.properties @@ -1,23 +1,8 @@ -GROUP=io.github.triniwiz.fancycamera - -POM_ARTIFACT_ID=imageLabeling -VERSION_NAME=1.0.1 - -POM_NAME=imageLabeling +POM_ARTIFACT_ID=image-labeling +POM_NAME=MLKit Image Labeling POM_PACKAGING=aar - POM_DESCRIPTION=Fancy Android Camera ImageLabeling -POM_INCEPTION_YEAR=2019 - -POM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git -POM_SCM_DEV_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git - -POM_LICENCE_NAME=The Apache Software License, Version 2.0 -POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt -POM_LICENCE_DIST=repo -POM_DEVELOPER_ID=triniwiz -POM_DEVELOPER_NAME=Osei Fortune -POM_DEVELOPER_URL=https://github.com/triniwiz \ No newline at end of file +# Prefix and postfix for source and javadoc jars. +JAR_PREFIX=fancycamera- +JAR_POSTFIX= \ No newline at end of file diff --git a/imageLabeling/publish.gradle b/imageLabeling/publish.gradle deleted file mode 100644 index 53954b1..0000000 --- a/imageLabeling/publish.gradle +++ /dev/null @@ -1,59 +0,0 @@ -apply plugin: 'signing' -apply plugin: 'maven-publish' -apply plugin: "com.vanniktech.maven.publish" - -allprojects { - plugins.withId("com.vanniktech.maven.publish") { - mavenPublish { - sonatypeHost = "S01" - } - } - - plugins.withId("com.vanniktech.maven.base") { - group = "io.github.triniwiz.fancycamera" - archivesBaseName = "imageLabeling" - version = "1.0.1" - - mavenPublishing { - publishToMavenCentral("DEFAULT") - - // Will only apply to non snapshot builds. - // Uses credentials as described above, supports both regular and in memory signing. - signAllPublications() - - pom { - name = "FancyCameraImageLabeling" - packaging = 'aar' - description = "Fancy Android Camera ImageLabeling" - inceptionYear = "2019" - url = 'https://github.com/triniwiz/fancycamera' - - licenses { - license { - name = "The Apache License, Version 2.0" - url = "http://www.apache.org/licenses/LICENSE-2.0.txt" - distribution = "http://www.apache.org/licenses/LICENSE-2.0.txt" - } - } - - developers { - developer { - id = 'triniwiz' - name = 'Osei Fortune' - email = 'fortune.osei@yahoo.com' - url = "https://github.com/triniwiz/" - } - } - - scm { - connection 'scm:git@github.com:triniwiz/fancycamera.git' - developerConnection 'scm:git@github.com:triniwiz/fancycamera.git' - url 'https://github.com/triniwiz/fancycamera' - } - } - - pomFromGradleProperties() - } - } -} - diff --git a/imageLabeling/src/main/AndroidManifest.xml b/imageLabeling/src/main/AndroidManifest.xml index a7e944f..a5918e6 100644 --- a/imageLabeling/src/main/AndroidManifest.xml +++ b/imageLabeling/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + \ No newline at end of file diff --git a/imageLabeling/src/main/java/io/github/triniwiz/fancycamera/imagelabeling/ImageLabeling.kt b/imageLabeling/src/main/java/io/github/triniwiz/fancycamera/imagelabeling/ImageLabeling.kt index 5d25ee2..2bc67ed 100644 --- a/imageLabeling/src/main/java/io/github/triniwiz/fancycamera/imagelabeling/ImageLabeling.kt +++ b/imageLabeling/src/main/java/io/github/triniwiz/fancycamera/imagelabeling/ImageLabeling.kt @@ -1,60 +1,48 @@ package io.github.triniwiz.fancycamera.imagelabeling -import android.graphics.Bitmap -import com.google.android.gms.tasks.Task -import com.google.android.gms.tasks.TaskCompletionSource +import com.google.android.gms.tasks.Tasks import com.google.gson.Gson import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.label.defaults.ImageLabelerOptions +import io.github.triniwiz.fancycamera.ImageProcessor import org.json.JSONObject -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors +import java.util.concurrent.ExecutionException +import java.util.concurrent.FutureTask -class ImageLabeling { - private var executor: ExecutorService = Executors.newSingleThreadExecutor() - fun processImage(image: InputImage, options: Options): Task { - val task = TaskCompletionSource() +class ImageLabeling : ImageProcessor { + var options = Options() + override val type: Int + get() = 2 + + private val gson = Gson() + override fun process(image: InputImage): FutureTask { val client = com.google.mlkit.vision.label.ImageLabeling.getClient( ImageLabelerOptions.Builder().setConfidenceThreshold(options.confidenceThreshold) .build() ) - val gson = Gson() - client.process(image) - .addOnSuccessListener(executor, { + + return FutureTask { + try { + val results = Tasks.await(client.process(image)) val result = mutableListOf() - for (label in it) { + for (label in results) { result.add(Result(label)) } - val json = if (result.isNotEmpty()) { + if (result.isNotEmpty()) { gson.toJson(result) } else { "" } + } catch (e: ExecutionException) { + throw e + } catch (e: InterruptedException) { + throw e + } finally { client.close() - task.setResult(json) - }) - .addOnFailureListener(executor, { - task.setException(it) - }) - return task.task - } - - fun processBytes( - bytes: ByteArray, - width: Int, - height: Int, - rotation: Int, - format: Int, - options: Options - ): Task { - val input = InputImage.fromByteArray(bytes, width, height, rotation, format) - return processImage(input, options) + } + } } - fun processBitmap(bitmap: Bitmap, rotation: Int, options: Options): Task { - val input = InputImage.fromBitmap(bitmap, rotation) - return processImage(input, options) - } class Options { var confidenceThreshold = 0.5f diff --git a/objectDetection/build.gradle b/objectDetection/build.gradle index 8a4c7b9..7505ec3 100644 --- a/objectDetection/build.gradle +++ b/objectDetection/build.gradle @@ -4,12 +4,13 @@ plugins { } android { - compileSdkVersion COMPILE_SDK_VERSION as int + compileSdk COMPILE_SDK_VERSION as int buildToolsVersion BUILD_TOOLS_VERSION as String defaultConfig { minSdkVersion 17 targetSdkVersion 33 + versionName = VERSION_NAME as String testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -22,22 +23,23 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } + namespace 'io.github.triniwiz.fancycamera.objectdetection' } dependencies { + implementation project(":fancycamera") implementation "com.google.code.gson:gson:$gson_version" - implementation 'com.google.mlkit:object-detection:17.0.0' - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'com.google.mlkit:object-detection:17.0.1' implementation "androidx.appcompat:appcompat:$app_compat_version" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } -apply from: 'publish.gradle' \ No newline at end of file +apply from: "${rootProject.projectDir}/scripts/upload.gradle" \ No newline at end of file diff --git a/objectDetection/gradle.properties b/objectDetection/gradle.properties index 9d090da..f0ff91a 100644 --- a/objectDetection/gradle.properties +++ b/objectDetection/gradle.properties @@ -1,23 +1,8 @@ -GROUP=io.github.triniwiz.fancycamera - -POM_ARTIFACT_ID=objectDetection -VERSION_NAME=1.0.2 - -POM_NAME=objectDetection +POM_ARTIFACT_ID=object-detection +POM_NAME=MLKit Object Detection POM_PACKAGING=aar - POM_DESCRIPTION=Fancy Android Camera ObjectDetection -POM_INCEPTION_YEAR=2019 - -POM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git -POM_SCM_DEV_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git - -POM_LICENCE_NAME=The Apache Software License, Version 2.0 -POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt -POM_LICENCE_DIST=repo -POM_DEVELOPER_ID=triniwiz -POM_DEVELOPER_NAME=Osei Fortune -POM_DEVELOPER_URL=https://github.com/triniwiz \ No newline at end of file +# Prefix and postfix for source and javadoc jars. +JAR_PREFIX=fancycamera- +JAR_POSTFIX= \ No newline at end of file diff --git a/objectDetection/publish.gradle b/objectDetection/publish.gradle deleted file mode 100644 index 1254926..0000000 --- a/objectDetection/publish.gradle +++ /dev/null @@ -1,58 +0,0 @@ -apply plugin: 'signing' -apply plugin: 'maven-publish' -apply plugin: "com.vanniktech.maven.publish" - -allprojects { - plugins.withId("com.vanniktech.maven.publish") { - mavenPublish { - sonatypeHost = "S01" - } - } - - plugins.withId("com.vanniktech.maven.base") { - group = "io.github.triniwiz.fancycamera" - archivesBaseName = "objectDetection" - version = "1.0.2" - - mavenPublishing { - publishToMavenCentral("DEFAULT") - - // Will only apply to non snapshot builds. - // Uses credentials as described above, supports both regular and in memory signing. - signAllPublications() - - pom { - name = "FancyCameraObjectDetection" - packaging = 'aar' - description = "Fancy Android Camera ObjectDetection" - inceptionYear = "2019" - url = 'https://github.com/triniwiz/fancycamera' - - licenses { - license { - name = "The Apache License, Version 2.0" - url = "http://www.apache.org/licenses/LICENSE-2.0.txt" - distribution = "http://www.apache.org/licenses/LICENSE-2.0.txt" - } - } - - developers { - developer { - id = 'triniwiz' - name = 'Osei Fortune' - email = 'fortune.osei@yahoo.com' - url = "https://github.com/triniwiz/" - } - } - - scm { - connection 'scm:git@github.com:triniwiz/fancycamera.git' - developerConnection 'scm:git@github.com:triniwiz/fancycamera.git' - url 'https://github.com/triniwiz/fancycamera' - } - } - - pomFromGradleProperties() - } - } -} diff --git a/objectDetection/src/main/AndroidManifest.xml b/objectDetection/src/main/AndroidManifest.xml index 3668928..a5918e6 100644 --- a/objectDetection/src/main/AndroidManifest.xml +++ b/objectDetection/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + \ No newline at end of file diff --git a/objectDetection/src/main/java/io/github/triniwiz/fancycamera/objectdetection/ObjectDetection.kt b/objectDetection/src/main/java/io/github/triniwiz/fancycamera/objectdetection/ObjectDetection.kt index 9981857..83d25c2 100644 --- a/objectDetection/src/main/java/io/github/triniwiz/fancycamera/objectdetection/ObjectDetection.kt +++ b/objectDetection/src/main/java/io/github/triniwiz/fancycamera/objectdetection/ObjectDetection.kt @@ -1,20 +1,23 @@ package io.github.triniwiz.fancycamera.objectdetection -import android.graphics.Bitmap -import com.google.android.gms.tasks.Task -import com.google.android.gms.tasks.TaskCompletionSource + +import com.google.android.gms.tasks.Tasks import com.google.gson.Gson import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.objects.defaults.ObjectDetectorOptions +import io.github.triniwiz.fancycamera.ImageProcessor import org.json.JSONObject -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors +import java.util.concurrent.ExecutionException +import java.util.concurrent.FutureTask + +class ObjectDetection : ImageProcessor { + var options = Options() + override val type: Int + get() = 3 -class ObjectDetection { - private var executor: ExecutorService = Executors.newSingleThreadExecutor() - fun processImage(image: InputImage, options: Options): Task { - val task = TaskCompletionSource() + private val gson = Gson() + override fun process(image: InputImage): FutureTask { val opts = ObjectDetectorOptions.Builder() if (options.multiple) { opts.enableMultipleObjects() @@ -29,44 +32,29 @@ class ObjectDetection { } val client = com.google.mlkit.vision.objects.ObjectDetection.getClient(opts.build()) - val gson = Gson() - client.process(image) - .addOnSuccessListener(executor) { + + return FutureTask { + try { + val results = Tasks.await(client.process(image)) val result = mutableListOf() - for (detected in it) { + for (detected in results) { result.add(Result(detected)) } - - val json = if (result.isNotEmpty()) { + if (result.isNotEmpty()) { gson.toJson(result) } else { "" } - task.setResult(json) - } - .addOnFailureListener(executor) { - task.setException(it) + } catch (e: ExecutionException) { + throw e + } catch (e: InterruptedException) { + throw e + } finally { + client.close() } - return task.task - } - - fun processBytes( - bytes: ByteArray, - width: Int, - height: Int, - rotation: Int, - format: Int, - options: Options - ): Task { - val input = InputImage.fromByteArray(bytes, width, height, rotation, format) - return processImage(input, options) + } } - fun processBitmap(bitmap: Bitmap, rotation: Int, options: Options): Task { - options.singleMode = true - val input = InputImage.fromBitmap(bitmap, rotation) - return processImage(input, options) - } class Options { var multiple = false diff --git a/objectDetection/src/main/java/io/github/triniwiz/fancycamera/objectdetection/Result.kt b/objectDetection/src/main/java/io/github/triniwiz/fancycamera/objectdetection/Result.kt index 01adb6a..1f63329 100644 --- a/objectDetection/src/main/java/io/github/triniwiz/fancycamera/objectdetection/Result.kt +++ b/objectDetection/src/main/java/io/github/triniwiz/fancycamera/objectdetection/Result.kt @@ -64,11 +64,12 @@ class Result(detectedObject: DetectedObject) { } class Bounds(rect: Rect) { - class Origin(val x: Int, val y: Int) - class Size(val width: Int, - val height: Int) + val x: Int = rect.left - val origin = Origin(rect.left, rect.top) - val size = Size(rect.width(), rect.height()) + val y: Int = rect.top + + val width: Int = rect.width() + + val height: Int = rect.height() } } diff --git a/poseDetection/build.gradle b/poseDetection/build.gradle index f53a1d5..4a72433 100644 --- a/poseDetection/build.gradle +++ b/poseDetection/build.gradle @@ -4,13 +4,15 @@ plugins { } android { - compileSdkVersion COMPILE_SDK_VERSION as int + compileSdk = COMPILE_SDK_VERSION as int buildToolsVersion BUILD_TOOLS_VERSION as String defaultConfig { minSdkVersion 17 targetSdkVersion 33 + versionName = VERSION_NAME as String + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } @@ -22,22 +24,23 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } + namespace 'io.github.triniwiz.fancycamera.posedetection' } dependencies { + implementation project(":fancycamera") implementation "com.google.code.gson:gson:$gson_version" - implementation 'com.google.mlkit:pose-detection:18.0.0-beta3' - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'com.google.mlkit:pose-detection:18.0.0-beta4' implementation "androidx.appcompat:appcompat:$app_compat_version" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } -apply from: 'publish.gradle' \ No newline at end of file +apply from: "${rootProject.projectDir}/scripts/upload.gradle" \ No newline at end of file diff --git a/poseDetection/gradle.properties b/poseDetection/gradle.properties index 1323719..fbea4fd 100644 --- a/poseDetection/gradle.properties +++ b/poseDetection/gradle.properties @@ -1,23 +1,8 @@ -GROUP=io.github.triniwiz.fancycamera - -POM_ARTIFACT_ID=poseDetection -VERSION_NAME=1.0.1 - -POM_NAME=poseDetection +POM_ARTIFACT_ID=pose-detection +POM_NAME=MLKit Pose Detection POM_PACKAGING=aar - POM_DESCRIPTION=Fancy Android Camera PoseDetection -POM_INCEPTION_YEAR=2019 - -POM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git -POM_SCM_DEV_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git - -POM_LICENCE_NAME=The Apache Software License, Version 2.0 -POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt -POM_LICENCE_DIST=repo -POM_DEVELOPER_ID=triniwiz -POM_DEVELOPER_NAME=Osei Fortune -POM_DEVELOPER_URL=https://github.com/triniwiz \ No newline at end of file +# Prefix and postfix for source and javadoc jars. +JAR_PREFIX=fancycamera- +JAR_POSTFIX= \ No newline at end of file diff --git a/poseDetection/publish.gradle b/poseDetection/publish.gradle deleted file mode 100644 index cd9d277..0000000 --- a/poseDetection/publish.gradle +++ /dev/null @@ -1,58 +0,0 @@ -apply plugin: 'signing' -apply plugin: 'maven-publish' -apply plugin: "com.vanniktech.maven.publish" - -allprojects { - plugins.withId("com.vanniktech.maven.publish") { - mavenPublish { - sonatypeHost = "S01" - } - } - - plugins.withId("com.vanniktech.maven.base") { - group = "io.github.triniwiz.fancycamera" - archivesBaseName = "poseDetection" - version = "1.0.1" - - mavenPublishing { - publishToMavenCentral("DEFAULT") - - // Will only apply to non snapshot builds. - // Uses credentials as described above, supports both regular and in memory signing. - signAllPublications() - - pom { - name = "FancyCameraPoseDetection" - packaging = 'aar' - description = "Fancy Android Camera PoseDetection" - inceptionYear = "2019" - url = 'https://github.com/triniwiz/fancycamera' - - licenses { - license { - name = "The Apache License, Version 2.0" - url = "http://www.apache.org/licenses/LICENSE-2.0.txt" - distribution = "http://www.apache.org/licenses/LICENSE-2.0.txt" - } - } - - developers { - developer { - id = 'triniwiz' - name = 'Osei Fortune' - email = 'fortune.osei@yahoo.com' - url = "https://github.com/triniwiz/" - } - } - - scm { - connection 'scm:git@github.com:triniwiz/fancycamera.git' - developerConnection 'scm:git@github.com:triniwiz/fancycamera.git' - url 'https://github.com/triniwiz/fancycamera' - } - } - - pomFromGradleProperties() - } - } -} diff --git a/poseDetection/src/main/AndroidManifest.xml b/poseDetection/src/main/AndroidManifest.xml index da4a54a..a5918e6 100644 --- a/poseDetection/src/main/AndroidManifest.xml +++ b/poseDetection/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + \ No newline at end of file diff --git a/poseDetection/src/main/java/io/github/triniwiz/fancycamera/posedetection/PoseDetection.kt b/poseDetection/src/main/java/io/github/triniwiz/fancycamera/posedetection/PoseDetection.kt index 396da68..55c68bf 100644 --- a/poseDetection/src/main/java/io/github/triniwiz/fancycamera/posedetection/PoseDetection.kt +++ b/poseDetection/src/main/java/io/github/triniwiz/fancycamera/posedetection/PoseDetection.kt @@ -1,55 +1,65 @@ package io.github.triniwiz.fancycamera.posedetection import android.graphics.Bitmap -import com.google.android.gms.tasks.Task -import com.google.android.gms.tasks.TaskCompletionSource +import com.google.android.gms.tasks.Tasks import com.google.gson.Gson import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.pose.defaults.PoseDetectorOptions -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors +import io.github.triniwiz.fancycamera.ImageProcessor +import java.util.concurrent.ExecutionException +import java.util.concurrent.FutureTask -class PoseDetection { - private var executor: ExecutorService = Executors.newSingleThreadExecutor() +class PoseDetection : ImageProcessor { var singleMode = false - fun processImage(image: InputImage): Task { - val task = TaskCompletionSource() + private val gson = Gson() + private var onlySingleMode = false + override val type: Int + get() = 4 + override fun process(image: InputImage): FutureTask { val opts = PoseDetectorOptions.Builder() - if (singleMode) { + if (singleMode || onlySingleMode) { opts.setDetectorMode(PoseDetectorOptions.SINGLE_IMAGE_MODE) } else { opts.setDetectorMode(PoseDetectorOptions.STREAM_MODE) } val client = com.google.mlkit.vision.pose.PoseDetection.getClient(opts.build()) - val gson = Gson() - client.process(image) - .addOnSuccessListener(executor, { - val result = Result(it) - val json: String = if (result.landMarks.isEmpty()) { - "" - } else { - gson.toJson(result) - } - client.close() - singleMode = false - task.setResult(json) - }) - .addOnFailureListener(executor, { - singleMode = false - task.setException(it) - }) - return task.task + + return FutureTask { + try { + val results = Tasks.await(client.process(image)) + val result = Result(results) + if (result.landMarks.isEmpty()) { + "" + } else { + gson.toJson(result) + } + } catch (e: ExecutionException) { + throw e + } catch (e: InterruptedException) { + throw e + } finally { + onlySingleMode = false + client.close() + } + } } - fun processBytes(bytes: ByteArray, width: Int, height: Int, rotation: Int, format: Int): Task { + override fun process( + bytes: ByteArray, + width: Int, + height: Int, + rotation: Int, + format: Int + ): FutureTask { + onlySingleMode = true val input = InputImage.fromByteArray(bytes, width, height, rotation, format) - return processImage(input) + return process(input) } - fun processBitmap(bitmap: Bitmap, rotation: Int): Task { - singleMode = true + override fun process(bitmap: Bitmap, rotation: Int): FutureTask { + onlySingleMode = true val input = InputImage.fromBitmap(bitmap, rotation) - return processImage(input) + return process(input) } } \ No newline at end of file diff --git a/scripts/upload.gradle b/scripts/upload.gradle new file mode 100644 index 0000000..39086c9 --- /dev/null +++ b/scripts/upload.gradle @@ -0,0 +1,214 @@ +/* + * Copyright 2013 Chris Banes + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Based on: https://github.com/mcxiaoke/gradle-mvn-push/blob/master/gradle-mvn-push.gradle. + * + * For faster runs add: -x check when building Glide. + */ + +apply plugin: 'maven-publish' +apply plugin: 'signing' + +version = VERSION_NAME +group = GROUP + +static def localMavenRepo() { + 'file://' + new File(System.getProperty('user.home'), '.m2/repository').absolutePath +} + +@SuppressWarnings("GrMethodMayBeStatic") +def isReleaseBuild() { + return !VERSION_NAME.contains("SNAPSHOT") +} + +def getReleaseRepositoryUrl() { + return hasProperty('LOCAL') ? localMavenRepo() + : hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL + : 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/' +} + +def getSnapshotRepositoryUrl() { + return hasProperty('LOCAL') ? localMavenRepo() + : hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL + : 'https://s01.oss.sonatype.org/content/repositories/snapshots/' +} + +def getRepositoryUsername() { + return hasProperty('USERNAME') ? USERNAME : (hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : '') +} + +def getRepositoryPassword() { + return hasProperty('PASSWORD') ? PASSWORD : (hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : '') +} + +afterEvaluate { project -> + def isAndroidProject = project.plugins.hasPlugin('com.android.application') || project.plugins.hasPlugin('com.android.library') + // To avoid uploading the default empty jar artifact in the project root directory, we use a custom + // configuration to specify which artifacts we want to upload. + publishing { + publications { + mavenJava(MavenPublication) { + groupId = GROUP + artifactId = POM_ARTIFACT_ID + version = VERSION_NAME + + repositories { + maven { + url = isReleaseBuild() ? getReleaseRepositoryUrl() : getSnapshotRepositoryUrl() + + credentials { + username = getRepositoryUsername() + password = getRepositoryPassword() + + } + } + } + + // Dependencies are only automatically included by the release plugin if the release + // variant is built. Since we've disabled the release variant to improve build + // times, we need to add the dependencies to the pom file explicitly. + if (isAndroidProject) { + pom.withXml { + def dependenciesNode = asNode().appendNode('dependencies') + + project.configurations.implementation.allDependencies.each { + def groupId = it.group + def artifactId = it.name + // If we specify an artifact id that differs from the project name, it won't + // match. To avoid that, we look up the artifact id (and group) by property + // for any project dependencies. + // TODO: there must be a neater way to do this. + if (it instanceof ProjectDependency) { + def properties = it.getDependencyProject().getProperties() + groupId = properties.get("GROUP") + artifactId = properties.get("POM_ARTIFACT_ID") + } + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', groupId) + dependencyNode.appendNode('artifactId', artifactId) + dependencyNode.appendNode('version', it.version) + dependencyNode.appendNode('scope', 'compile') + } + } + } + + pom { + name = POM_NAME + packaging = POM_PACKAGING + description = POM_DESCRIPTION + url = POM_URL + + scm { + connection = POM_SCM_CONNECTION + developerConnection = POM_SCM_DEV_CONNECTION + url = POM_SCM_URL + } + + licenses { + license { + name = 'The Apache Software License, Version 2.0' + url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution = 'repo' + } + } + + developers { + developer { + id = POM_DEVELOPER_ID + name = POM_DEVELOPER_NAME + email = POM_DEVELOPER_EMAIL + } + } + } + + if (isAndroidProject) { + def variants = project.android.libraryVariants.findAll { + it.buildType.name.equalsIgnoreCase('release') + } + + def getAndroidSdkDirectory = project.android.sdkDirectory + + def getAndroidJar = "${getAndroidSdkDirectory}/platforms/${project.android.compileSdkVersion}/android.jar" + + task androidJavadocs(type: Javadoc, dependsOn: assembleRelease) { + source = variants.collect { it.getJavaCompileProvider().get().source } + classpath = files( + getAndroidJar, + project.file("build/intermediates/javac/release/classes") + ) + doFirst { + classpath += files(variants.collect { it.getJavaCompileProvider().get().classpath.files }) + } + options { + links("http://docs.oracle.com/javase/7/docs/api/") + links("http://d.android.com/reference") + } + + exclude '**/R.java' + } + + def cleanJavadocTask = task("cleanJavadocTask", type: Delete) { + delete androidJavadocs.destinationDir + } as Task + project.clean.dependsOn(cleanJavadocTask) + + task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { + archiveClassifier.set('javadoc') + from androidJavadocs.destinationDir + archiveBaseName.set("${JAR_PREFIX}${project.name}${JAR_POSTFIX}") + } + + task androidSourcesJar(type: Jar) { + archiveClassifier.set('sources') + from project.android.sourceSets.main.java.source + archiveBaseName.set("${JAR_PREFIX}${project.name}${JAR_POSTFIX}") + } + + task androidLibraryJar(type: Jar, dependsOn: compileReleaseJavaWithJavac /* == variant.javaCompile */) { + from compileReleaseJavaWithJavac.destinationDir + exclude '**/R.class' + exclude '**/R$*.class' + archiveBaseName.set("${JAR_PREFIX}${project.name}${JAR_POSTFIX}") + } + + artifact androidLibraryJar + artifact androidSourcesJar + artifact androidJavadocsJar + artifact project.tasks.bundleReleaseAar + } else if (project.plugins.hasPlugin('java')) { + task sourcesJar(type: Jar, dependsOn: classes) { + archiveClassifier.set('sources') + from sourceSets.main.allSource + } + + task javadocsJar(type: Jar, dependsOn: javadoc) { + archiveClassifier.set('javadoc') + from javadoc.destinationDir + } + + from components.java + artifact sourcesJar + artifact javadocsJar + } + } + } + } + + signing { + required { isReleaseBuild() } + sign publishing.publications + } +} \ No newline at end of file diff --git a/selfieSegmentation/build.gradle b/selfieSegmentation/build.gradle index 29973bd..fbd97cc 100644 --- a/selfieSegmentation/build.gradle +++ b/selfieSegmentation/build.gradle @@ -4,13 +4,15 @@ plugins { } android { - compileSdkVersion COMPILE_SDK_VERSION as int + compileSdk = COMPILE_SDK_VERSION as int buildToolsVersion BUILD_TOOLS_VERSION as String defaultConfig { minSdk 17 targetSdk 33 + versionName = VERSION_NAME as String + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } @@ -22,21 +24,24 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } + namespace 'io.github.triniwiz.fancycamera.selfiesegmentation' } dependencies { + implementation project(":fancycamera") implementation "com.google.code.gson:gson:$gson_version" - implementation 'com.google.mlkit:segmentation-selfie:16.0.0-beta4' - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.mlkit:segmentation-selfie:16.0.0-beta5' + implementation "androidx.appcompat:appcompat:$app_compat_version" + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } -apply from: 'publish.gradle' \ No newline at end of file +apply from: "${rootProject.projectDir}/scripts/upload.gradle" \ No newline at end of file diff --git a/selfieSegmentation/gradle.properties b/selfieSegmentation/gradle.properties index 317d203..35c561b 100644 --- a/selfieSegmentation/gradle.properties +++ b/selfieSegmentation/gradle.properties @@ -1,23 +1,8 @@ -GROUP=io.github.triniwiz.fancycamera - -POM_ARTIFACT_ID=selfieSegmentation -VERSION_NAME=1.0.1 - -POM_NAME=selfieSegmentation +POM_ARTIFACT_ID=selfie-segmentation +POM_NAME=MLKit Selfie Segmentation POM_PACKAGING=aar - POM_DESCRIPTION=Fancy Android Camera SelfieSegmentation -POM_INCEPTION_YEAR=2019 - -POM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git -POM_SCM_DEV_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git - -POM_LICENCE_NAME=The Apache Software License, Version 2.0 -POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt -POM_LICENCE_DIST=repo -POM_DEVELOPER_ID=triniwiz -POM_DEVELOPER_NAME=Osei Fortune -POM_DEVELOPER_URL=https://github.com/triniwiz \ No newline at end of file +# Prefix and postfix for source and javadoc jars. +JAR_PREFIX=fancycamera- +JAR_POSTFIX= \ No newline at end of file diff --git a/selfieSegmentation/publish.gradle b/selfieSegmentation/publish.gradle deleted file mode 100644 index 92cb92a..0000000 --- a/selfieSegmentation/publish.gradle +++ /dev/null @@ -1,61 +0,0 @@ -apply plugin: 'signing' -apply plugin: 'maven-publish' -apply plugin: "com.vanniktech.maven.publish" - -allprojects { - plugins.withId("com.vanniktech.maven.publish") { - mavenPublish { - sonatypeHost = "S01" - } - } - - plugins.withId("com.vanniktech.maven.base") { - group = "io.github.triniwiz.fancycamera" - archivesBaseName = "selfieSegmentation" - version = "1.0.0" - - mavenPublishing { - publishToMavenCentral("DEFAULT") - - // Will only apply to non snapshot builds. - // Uses credentials as described above, supports both regular and in memory signing. - signAllPublications() - - pom { - name = "FancyCameraSelfieSegmentation" - packaging = 'aar' - description = "Fancy Android Camera SelfieSegmentation" - inceptionYear = "2019" - url = 'https://github.com/triniwiz/fancycamera' - - licenses { - license { - name = "The Apache License, Version 2.0" - url = "http://www.apache.org/licenses/LICENSE-2.0.txt" - distribution = "http://www.apache.org/licenses/LICENSE-2.0.txt" - } - } - - developers { - developer { - id = 'triniwiz' - name = 'Osei Fortune' - email = 'fortune.osei@yahoo.com' - url = "https://github.com/triniwiz/" - } - } - - scm { - connection 'scm:git@github.com:triniwiz/fancycamera.git' - developerConnection 'scm:git@github.com:triniwiz/fancycamera.git' - url 'https://github.com/triniwiz/fancycamera' - } - } - - pomFromGradleProperties() - } - } -} - - - diff --git a/selfieSegmentation/src/main/AndroidManifest.xml b/selfieSegmentation/src/main/AndroidManifest.xml index 3015feb..a5918e6 100644 --- a/selfieSegmentation/src/main/AndroidManifest.xml +++ b/selfieSegmentation/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + \ No newline at end of file diff --git a/selfieSegmentation/src/main/java/io/github/triniwiz/fancycamera/selfiesegmentation/SelfieSegmentation.kt b/selfieSegmentation/src/main/java/io/github/triniwiz/fancycamera/selfiesegmentation/SelfieSegmentation.kt index abb4721..fdebc5f 100644 --- a/selfieSegmentation/src/main/java/io/github/triniwiz/fancycamera/selfiesegmentation/SelfieSegmentation.kt +++ b/selfieSegmentation/src/main/java/io/github/triniwiz/fancycamera/selfiesegmentation/SelfieSegmentation.kt @@ -1,22 +1,24 @@ package io.github.triniwiz.fancycamera.selfiesegmentation import android.graphics.Bitmap -import android.util.Log -import com.google.android.gms.tasks.Task -import com.google.android.gms.tasks.TaskCompletionSource +import com.google.android.gms.tasks.Tasks import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.segmentation.Segmentation import com.google.mlkit.vision.segmentation.SegmentationMask -import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions; +import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions +import io.github.triniwiz.fancycamera.ImageProcessor import org.json.JSONObject -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors +import java.util.concurrent.ExecutionException +import java.util.concurrent.FutureTask -class SelfieSegmentation { - private var executor: ExecutorService = Executors.newSingleThreadExecutor() +class SelfieSegmentation : ImageProcessor { + var options = Options() + override val type: Int + get() = 5 + private var onlySingleMode = false - fun processImage(image: InputImage, options: Options): Task { - val task = TaskCompletionSource() + + override fun process(image: InputImage): FutureTask { val opts = SelfieSegmenterOptions.Builder() if (options.singleMode) { opts.setDetectorMode(SelfieSegmenterOptions.SINGLE_IMAGE_MODE) @@ -28,34 +30,38 @@ class SelfieSegmentation { } opts.setStreamModeSmoothingRatio(options.smoothingRatio) val client = Segmentation.getClient(opts.build()) - client.process(image) - .addOnCompleteListener(executor) { + + return FutureTask { + try { + Tasks.await(client.process(image)) + } catch (e: ExecutionException) { + throw e + } catch (e: InterruptedException) { + throw e + } finally { + onlySingleMode = false client.close() - task.setResult(it.result) - }.addOnFailureListener(executor) { - task.setException(it) } - return task.task - } + } + } - fun processBytes( + override fun process( bytes: ByteArray, width: Int, height: Int, rotation: Int, - format: Int, - options: Options - ): Task { + format: Int + ): FutureTask { + onlySingleMode = true val input = InputImage.fromByteArray(bytes, width, height, rotation, format) - options.singleMode = true - return processImage(input, options) + return process(input) } - fun processBitmap(bitmap: Bitmap, rotation: Int, options: Options): Task { - options.singleMode = true + override fun process(bitmap: Bitmap, rotation: Int): FutureTask { + onlySingleMode = true val input = InputImage.fromBitmap(bitmap, rotation) - return processImage(input, options) + return process(input) } class Options { diff --git a/textRecognition/build.gradle b/textRecognition/build.gradle index 1b90665..0033a48 100644 --- a/textRecognition/build.gradle +++ b/textRecognition/build.gradle @@ -4,13 +4,15 @@ plugins { } android { - compileSdkVersion COMPILE_SDK_VERSION as int + compileSdk = COMPILE_SDK_VERSION as int buildToolsVersion BUILD_TOOLS_VERSION as String defaultConfig { minSdkVersion 17 targetSdkVersion 33 + versionName = VERSION_NAME as String + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } @@ -22,22 +24,23 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } + namespace 'io.github.triniwiz.fancycamera.textrecognition' } dependencies { + implementation project(":fancycamera") implementation "com.google.code.gson:gson:$gson_version" - implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2' - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'com.google.android.gms:play-services-mlkit-text-recognition:19.0.0' implementation "androidx.appcompat:appcompat:$app_compat_version" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } -apply from: 'publish.gradle' \ No newline at end of file +apply from: "${rootProject.projectDir}/scripts/upload.gradle" \ No newline at end of file diff --git a/textRecognition/gradle.properties b/textRecognition/gradle.properties index 34d36fc..29e9ac1 100644 --- a/textRecognition/gradle.properties +++ b/textRecognition/gradle.properties @@ -1,23 +1,9 @@ -GROUP=io.github.triniwiz.fancycamera - -POM_ARTIFACT_ID=textRecognition -VERSION_NAME=1.0.2 - -POM_NAME=textRecognition +POM_ARTIFACT_ID=text-recognition +POM_NAME=MLKit Text Recognition POM_PACKAGING=aar - POM_DESCRIPTION=Fancy Android Camera TextRecognition -POM_INCEPTION_YEAR=2019 - -POM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_URL=https://github.com/triniwiz/fancycamera -POM_SCM_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git -POM_SCM_DEV_CONNECTION=scm:git@github.com:triniwiz/fancycamera.git -POM_LICENCE_NAME=The Apache Software License, Version 2.0 -POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt -POM_LICENCE_DIST=repo -POM_DEVELOPER_ID=triniwiz -POM_DEVELOPER_NAME=Osei Fortune -POM_DEVELOPER_URL=https://github.com/triniwiz \ No newline at end of file +# Prefix and postfix for source and javadoc jars. +JAR_PREFIX=fancycamera- +JAR_POSTFIX= \ No newline at end of file diff --git a/textRecognition/publish.gradle b/textRecognition/publish.gradle deleted file mode 100644 index 0bc32b0..0000000 --- a/textRecognition/publish.gradle +++ /dev/null @@ -1,61 +0,0 @@ -apply plugin: 'signing' -apply plugin: 'maven-publish' -apply plugin: "com.vanniktech.maven.publish" - -allprojects { - plugins.withId("com.vanniktech.maven.publish") { - mavenPublish { - sonatypeHost = "S01" - } - } - - plugins.withId("com.vanniktech.maven.base") { - group = "io.github.triniwiz.fancycamera" - archivesBaseName = "textRecognition" - version = "1.0.2" - - mavenPublishing { - publishToMavenCentral("DEFAULT") - - // Will only apply to non snapshot builds. - // Uses credentials as described above, supports both regular and in memory signing. - signAllPublications() - - pom { - name = "FancyCameraTextRecognition" - packaging = 'aar' - description = "Fancy Android Camera TextRecognition" - inceptionYear = "2019" - url = 'https://github.com/triniwiz/fancycamera' - - licenses { - license { - name = "The Apache License, Version 2.0" - url = "http://www.apache.org/licenses/LICENSE-2.0.txt" - distribution = "http://www.apache.org/licenses/LICENSE-2.0.txt" - } - } - - developers { - developer { - id = 'triniwiz' - name = 'Osei Fortune' - email = 'fortune.osei@yahoo.com' - url = "https://github.com/triniwiz/" - } - } - - scm { - connection 'scm:git@github.com:triniwiz/fancycamera.git' - developerConnection 'scm:git@github.com:triniwiz/fancycamera.git' - url 'https://github.com/triniwiz/fancycamera' - } - } - - pomFromGradleProperties() - } - } -} - - - diff --git a/textRecognition/src/main/AndroidManifest.xml b/textRecognition/src/main/AndroidManifest.xml index dd3f15f..a5918e6 100644 --- a/textRecognition/src/main/AndroidManifest.xml +++ b/textRecognition/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + \ No newline at end of file diff --git a/textRecognition/src/main/java/io/github/triniwiz/fancycamera/textrecognition/Result.kt b/textRecognition/src/main/java/io/github/triniwiz/fancycamera/textrecognition/Result.kt index 0c6018c..c757a02 100644 --- a/textRecognition/src/main/java/io/github/triniwiz/fancycamera/textrecognition/Result.kt +++ b/textRecognition/src/main/java/io/github/triniwiz/fancycamera/textrecognition/Result.kt @@ -43,12 +43,13 @@ class Result(instance: Text) { class Line(val text: String, val cornerPoints: Array, bounds: Bounds, elements: Array) class Block(val text: String, val cornerPoints: Array, bounds: Bounds, lines: Array) class Bounds(rect: Rect) { - class Origin(val x: Int, val y: Int) - class Size(val width: Int, - val height: Int) + val x: Int = rect.left - val origin = Origin(rect.left, rect.top) - val size = Size(rect.width(), rect.height()) + val y: Int = rect.top + + val width: Int = rect.width() + + val height: Int = rect.height() } class Point(point: android.graphics.Point) { diff --git a/textRecognition/src/main/java/io/github/triniwiz/fancycamera/textrecognition/TextRecognition.kt b/textRecognition/src/main/java/io/github/triniwiz/fancycamera/textrecognition/TextRecognition.kt index 123dc90..e14a8f9 100644 --- a/textRecognition/src/main/java/io/github/triniwiz/fancycamera/textrecognition/TextRecognition.kt +++ b/textRecognition/src/main/java/io/github/triniwiz/fancycamera/textrecognition/TextRecognition.kt @@ -1,43 +1,36 @@ package io.github.triniwiz.fancycamera.textrecognition -import android.graphics.Bitmap -import com.google.android.gms.tasks.Task -import com.google.android.gms.tasks.TaskCompletionSource +import com.google.android.gms.tasks.Tasks import com.google.gson.Gson import com.google.mlkit.vision.common.InputImage -import com.google.mlkit.vision.text.latin.TextRecognizerOptions; +import com.google.mlkit.vision.text.latin.TextRecognizerOptions +import io.github.triniwiz.fancycamera.ImageProcessor +import java.util.concurrent.ExecutionException +import java.util.concurrent.FutureTask -import java.util.concurrent.Executors - -class TextRecognition { - private val executor = Executors.newSingleThreadExecutor() - fun processImage(image: InputImage): Task { - val task = TaskCompletionSource() - val client = com.google.mlkit.vision.text.TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) - val gson = Gson() - client.process(image) - .addOnSuccessListener(executor, { - val result = Result(it) - val json: String = if (result.text.isEmpty() && result.blocks.isEmpty()) { - "" - } else { - gson.toJson(result) - } - task.setResult(json) - }) - .addOnFailureListener(executor, { - task.setException(it) - }) - return task.task - } - - fun processBytes(bytes: ByteArray, width: Int, height: Int, rotation: Int, format: Int): Task { - val input = InputImage.fromByteArray(bytes, width, height, rotation, format) - return processImage(input) - } - - fun processBitmap(bitmap: Bitmap, rotation: Int): Task { - val input = InputImage.fromBitmap(bitmap, rotation) - return processImage(input) +class TextRecognition : ImageProcessor { + override val type: Int + get() = 6 + private val gson = Gson() + override fun process(image: InputImage): FutureTask { + val client = + com.google.mlkit.vision.text.TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) + return FutureTask { + try { + val results = Tasks.await(client.process(image)) + val result = Result(results) + if (result.text.isEmpty() && result.blocks.isEmpty()) { + "" + } else { + gson.toJson(result) + } + } catch (e: ExecutionException) { + throw e + } catch (e: InterruptedException) { + throw e + } finally { + client.close() + } + } } } \ No newline at end of file From 062ad3b7f0db014de0d7296a40076b3e73371475 Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Mon, 22 Apr 2024 04:11:22 -0400 Subject: [PATCH 2/6] chore: clean up --- build.gradle | 8 -------- fancycamera/build.gradle | 3 ++- gradle.properties | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index e0b17bc..e564e8c 100644 --- a/build.gradle +++ b/build.gradle @@ -10,18 +10,10 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:8.3.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.vanniktech:gradle-maven-publish-plugin:0.18.0' - classpath 'org.jetbrains.dokka:dokka-gradle-plugin:1.4.10.2' } } allprojects { - plugins.withId("com.vanniktech.maven.publish") { - mavenPublish { - sonatypeHost = "S01" - } - } - repositories { google() mavenCentral() diff --git a/fancycamera/build.gradle b/fancycamera/build.gradle index 0760357..90c9cdc 100644 --- a/fancycamera/build.gradle +++ b/fancycamera/build.gradle @@ -2,11 +2,12 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion COMPILE_SDK_VERSION as int + compileSdk = COMPILE_SDK_VERSION as int buildToolsVersion BUILD_TOOLS_VERSION as String ndkVersion '23.2.8568313' defaultConfig { minSdkVersion 17 + versionName = VERSION_NAME as String testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/gradle.properties b/gradle.properties index d070ad5..ea34c84 100644 --- a/gradle.properties +++ b/gradle.properties @@ -45,7 +45,7 @@ POM_INCEPTION_YEAR=2019 VERSION_MAJOR=4 VERSION_MINOR=0 VERSION_PATCH=0 -VERSION_NAME=4.0.0-ALPHA.0 +VERSION_NAME=4.0.0-alpha.1 android.defaults.buildfeatures.buildconfig=true android.nonTransitiveRClass=false android.nonFinalResIds=false \ No newline at end of file From 17346fcf4b93641935d58fd2aa7ca1b9f39aadfa Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Mon, 22 Apr 2024 05:03:54 -0400 Subject: [PATCH 3/6] fix: release build --- fancycamera/build.gradle | 2 +- gradle.properties | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/fancycamera/build.gradle b/fancycamera/build.gradle index 90c9cdc..84ae9f6 100644 --- a/fancycamera/build.gradle +++ b/fancycamera/build.gradle @@ -27,7 +27,7 @@ android { dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) +// implementation fileTree(dir: 'libs', include: ['*.jar']) api "androidx.appcompat:appcompat:$app_compat_version" implementation 'com.google.mlkit:vision-common:17.3.0' implementation 'androidx.databinding:databinding-runtime:8.3.2' diff --git a/gradle.properties b/gradle.properties index ea34c84..2f2755f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,12 +19,12 @@ BUILD_TOOLS_VERSION=34 COMPILE_SDK_VERSION=34 android.useAndroidX=true android.enableJetifier=true -camerax_version = 1.4.0-alpha05 -camerax_view_version = 1.4.0-alpha05 -camerax_ext_version = 1.4.0-alpha05 -camerax_lifecycle_version = 1.4.0-alpha05 -gson_version = 2.10.1 -app_compat_version = 1.6.1 +camerax_version=1.4.0-alpha05 +camerax_view_version=1.4.0-alpha05 +camerax_ext_version=1.4.0-alpha05 +camerax_lifecycle_version=1.4.0-alpha05 +gson_version=2.10.1 +app_compat_version=1.6.1 @@ -45,7 +45,6 @@ POM_INCEPTION_YEAR=2019 VERSION_MAJOR=4 VERSION_MINOR=0 VERSION_PATCH=0 -VERSION_NAME=4.0.0-alpha.1 -android.defaults.buildfeatures.buildconfig=true +VERSION_NAME=4.0.0-alpha.3 android.nonTransitiveRClass=false android.nonFinalResIds=false \ No newline at end of file From a19d7717690799645d22cf6aa2a170e980fcc09c Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Fri, 24 May 2024 09:28:16 -0400 Subject: [PATCH 4/6] fix: mlkit --- app/src/main/AndroidManifest.xml | 1 + .../triniwiz/videorecorder/MainActivity.kt | 23 +- app/src/main/res/layout/activity_main.xml | 9 +- .../fancycamera/barcodescanning/Result.kt | 52 ++++ .../io/github/triniwiz/fancycamera/Camera.kt | 5 + .../io/github/triniwiz/fancycamera/Camera2.kt | 261 ++++++++++++++---- .../github/triniwiz/fancycamera/CameraBase.kt | 2 + .../triniwiz/fancycamera/FancyCamera.kt | 5 +- .../java/io/github/triniwiz/fancycamera/ML.kt | 7 +- gradle.properties | 2 +- .../fancycamera/objectdetection/Result.kt | 23 ++ .../fancycamera/posedetection/Result.kt | 72 ++++- 12 files changed, 388 insertions(+), 74 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2e994f4..e96a6a4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ > + app:layout_constraintTop_toTopOf="parent" />