diff --git a/CameraX-MLKit/README.md b/CameraX-MLKit/README.md
index 4df2e048..409a36bc 100644
--- a/CameraX-MLKit/README.md
+++ b/CameraX-MLKit/README.md
@@ -1,8 +1,14 @@
# CameraX-MLKit
-This example uses CameraX's MlKitAnalyzer to perform QR Code scanning. For QR Codes that encode Urls, this app will prompt the user to open the Url in a broswer. This app can be adapted to handle other types of QR Code data.
+This example uses CameraX's MlKitAnalyzer to perform QR Code scanning. For QR Codes that encode Urls, this app will prompt the user to open the Url in
+a broswer. This app can be adapted to handle other types of QR Code data. This example also uses CameraX's MlKitAnalyzer to perform Text Recognition.
-The interesting part of the code is in `MainActivity.kt` in the `startCamera()` function. There, we set up BarcodeScannerOptions to match on QR Codes. Then we call `cameraController.setImageAnalysisAnalyzer` with an `MlKitAnalyzer` (available as of CameraX 1.2). We also pass in `COORDINATE_SYSTEM_VIEW_REFERENCED` so that CameraX will handle the cordinates coming off of the camera sensor, making it easy to draw a box around the QR Code. Finally, we create a QrCodeDrawable, which is a class defined in this sample, extending View, for displaying an overlay on the QR Code and handling tap events on the QR Code.
+On `onCreate()` we set up BarcodeScannerOptions to match on QR Codes and TextRecognizerOptions to match on Text on image.
+
+The interesting part of the code is in `MainActivity.kt` in the `startCamera()` function. Then we call `cameraController.setImageAnalysisAnalyzer`
+with an `MlKitAnalyzer` (available as of CameraX 1.2). We also pass in `COORDINATE_SYSTEM_VIEW_REFERENCED` so that CameraX will handle the cordinates
+coming off of the camera sensor, making it easy to draw a box around the QR Code. Finally, we create a QrCodeDrawable, which is a class defined in
+this sample, extending View, for displaying an overlay on the QR Code and handling tap events on the QR Code.
You can open this project in Android Studio to explore the code further, and to build and run the application on a test device.
@@ -10,11 +16,11 @@ You can open this project in Android Studio to explore the code further, and to
-## Command line options
+## Command line options
### Build
-To build the app directly from the command line, run:
+To build the app directly from the command line, run: ''
```sh
./gradlew assembleDebug
```
diff --git a/CameraX-MLKit/app/build.gradle b/CameraX-MLKit/app/build.gradle
index ff5a4f5c..29614370 100644
--- a/CameraX-MLKit/app/build.gradle
+++ b/CameraX-MLKit/app/build.gradle
@@ -69,4 +69,5 @@ dependencies {
implementation "androidx.camera:camera-view:${camerax_version}"
implementation 'com.google.mlkit:barcode-scanning:17.0.2'
+ implementation 'com.google.mlkit:text-recognition:16.0.0-beta6'
}
\ No newline at end of file
diff --git a/CameraX-MLKit/app/src/main/java/com/example/camerax_mlkit/MainActivity.kt b/CameraX-MLKit/app/src/main/java/com/example/camerax_mlkit/MainActivity.kt
index e47a5e7b..ccaa9d2c 100644
--- a/CameraX-MLKit/app/src/main/java/com/example/camerax_mlkit/MainActivity.kt
+++ b/CameraX-MLKit/app/src/main/java/com/example/camerax_mlkit/MainActivity.kt
@@ -18,21 +18,26 @@ package com.example.camerax_mlkit
import android.Manifest
import android.content.pm.PackageManager
+import android.os.Build
import android.os.Bundle
-import android.view.View
import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.mlkit.vision.MlKitAnalyzer
import androidx.camera.view.CameraController.COORDINATE_SYSTEM_VIEW_REFERENCED
import androidx.camera.view.LifecycleCameraController
import androidx.camera.view.PreviewView
-import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.camerax_mlkit.databinding.ActivityMainBinding
+import com.example.camerax_mlkit.utils.BuildRect
+import com.google.android.material.snackbar.Snackbar
import com.google.mlkit.vision.barcode.BarcodeScanner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
+import com.google.mlkit.vision.text.TextRecognition
+import com.google.mlkit.vision.text.TextRecognizer
+import com.google.mlkit.vision.text.latin.TextRecognizerOptions
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
@@ -40,56 +45,73 @@ class MainActivity : AppCompatActivity() {
private lateinit var viewBinding: ActivityMainBinding
private lateinit var cameraExecutor: ExecutorService
private lateinit var barcodeScanner: BarcodeScanner
+ private lateinit var textRecognizer: TextRecognizer
+
+ companion object {
+ private const val TAG = "CameraX-MLKit"
+ private const val REQUEST_CODE_PERMISSIONS = 10
+ private val REQUIRED_PERMISSIONS =
+ mutableListOf(Manifest.permission.CAMERA).toTypedArray()
+ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
+ cameraExecutor = Executors.newSingleThreadExecutor()
+ val options = BarcodeScannerOptions.Builder()
+ .setBarcodeFormats(Barcode.FORMAT_QR_CODE)
+ .build()
+
+ val textOptions = TextRecognizerOptions.Builder().build()
+ barcodeScanner = BarcodeScanning.getClient(options)
+ textRecognizer = TextRecognition.getClient(textOptions)
+ }
+
+ override fun onStart() {
+ super.onStart()
// Request camera permissions
if (allPermissionsGranted()) {
startCamera()
} else {
- ActivityCompat.requestPermissions(
- this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
- )
+ requestCameraPermission()
}
-
- cameraExecutor = Executors.newSingleThreadExecutor()
}
private fun startCamera() {
- var cameraController = LifecycleCameraController(baseContext)
+ val cameraController = LifecycleCameraController(baseContext)
val previewView: PreviewView = viewBinding.viewFinder
- val options = BarcodeScannerOptions.Builder()
- .setBarcodeFormats(Barcode.FORMAT_QR_CODE)
- .build()
- barcodeScanner = BarcodeScanning.getClient(options)
-
cameraController.setImageAnalysisAnalyzer(
ContextCompat.getMainExecutor(this),
MlKitAnalyzer(
- listOf(barcodeScanner),
+ listOf(barcodeScanner, textRecognizer),
COORDINATE_SYSTEM_VIEW_REFERENCED,
ContextCompat.getMainExecutor(this)
) { result: MlKitAnalyzer.Result? ->
+ val textResults = result?.getValue(textRecognizer)
val barcodeResults = result?.getValue(barcodeScanner)
- if ((barcodeResults == null) ||
- (barcodeResults.size == 0) ||
- (barcodeResults.first() == null)
- ) {
+
+ previewView.overlay.clear()
+
+ barcodeResults?.getOrNull(0)?.let {
+ val qrCodeViewModel = QrCodeViewModel(it)
+ val qrCodeDrawable = BuildRect(qrCodeViewModel.boundingRect, qrCodeViewModel.qrContent)
+ previewView.setOnTouchListener(qrCodeViewModel.qrCodeTouchCallback)
+ previewView.overlay.add(qrCodeDrawable)
+ } ?: kotlin.run {
previewView.overlay.clear()
previewView.setOnTouchListener { _, _ -> false } //no-op
- return@MlKitAnalyzer
}
- val qrCodeViewModel = QrCodeViewModel(barcodeResults[0])
- val qrCodeDrawable = QrCodeDrawable(qrCodeViewModel)
-
- previewView.setOnTouchListener(qrCodeViewModel.qrCodeTouchCallback)
- previewView.overlay.clear()
- previewView.overlay.add(qrCodeDrawable)
+ textResults?.textBlocks?.flatMap { it.lines }?.forEach {
+ val textViewModel = TextViewModel(it)
+ val textDrawable = BuildRect(textViewModel.boundingRect, textViewModel.lineContent)
+ previewView.overlay.add(textDrawable)
+ } ?: kotlin.run {
+ previewView.overlay.clear()
+ }
}
)
@@ -98,38 +120,37 @@ class MainActivity : AppCompatActivity() {
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
- ContextCompat.checkSelfPermission(
- baseContext, it) == PackageManager.PERMISSION_GRANTED
+ ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
barcodeScanner.close()
+ textRecognizer.close()
}
- companion object {
- private const val TAG = "CameraX-MLKit"
- private const val REQUEST_CODE_PERMISSIONS = 10
- private val REQUIRED_PERMISSIONS =
- mutableListOf (
- Manifest.permission.CAMERA
- ).toTypedArray()
+ private val requestMultiplePermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
+ if (REQUIRED_PERMISSIONS.all { result[it] == true }) {
+ startCamera()
+ } else {
+ requestCameraPermission()
+ }
}
- override fun onRequestPermissionsResult(
- requestCode: Int, permissions: Array, grantResults:
- IntArray) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- if (requestCode == REQUEST_CODE_PERMISSIONS) {
- if (allPermissionsGranted()) {
- startCamera()
+ private fun requestCameraPermission() {
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
+ if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
+ Snackbar.make(viewBinding.myConstraintLayout, "You need to provide camera permission to use this.", Snackbar.LENGTH_INDEFINITE)
+ .setAction("Request Camera Permission") {
+ requestMultiplePermissionLauncher.launch(REQUIRED_PERMISSIONS)
+ }.show()
} else {
- Toast.makeText(this,
- "Permissions not granted by the user.",
- Toast.LENGTH_SHORT).show()
- finish()
+ requestMultiplePermissionLauncher.launch(REQUIRED_PERMISSIONS)
}
+ } else {
+ Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show()
+ finish()
}
}
}
\ No newline at end of file
diff --git a/CameraX-MLKit/app/src/main/java/com/example/camerax_mlkit/QrCodeDrawable.kt b/CameraX-MLKit/app/src/main/java/com/example/camerax_mlkit/QrCodeDrawable.kt
deleted file mode 100644
index d2c5a4d6..00000000
--- a/CameraX-MLKit/app/src/main/java/com/example/camerax_mlkit/QrCodeDrawable.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * 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
- *
- * https://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.
- */
-
-package com.example.camerax_mlkit
-
-import android.content.Intent
-import android.graphics.*
-import android.graphics.drawable.Drawable
-import android.net.Uri
-import android.view.MotionEvent
-import android.view.View
-import com.google.mlkit.vision.barcode.common.Barcode
-
-/**
- * A Drawable that handles displaying a QR Code's data and a bounding box around the QR code.
- */
-class QrCodeDrawable(qrCodeViewModel: QrCodeViewModel) : Drawable() {
- private val boundingRectPaint = Paint().apply {
- style = Paint.Style.STROKE
- color = Color.YELLOW
- strokeWidth = 5F
- alpha = 200
- }
-
- private val contentRectPaint = Paint().apply {
- style = Paint.Style.FILL
- color = Color.YELLOW
- alpha = 255
- }
-
- private val contentTextPaint = Paint().apply {
- color = Color.DKGRAY
- alpha = 255
- textSize = 36F
- }
-
- private val qrCodeViewModel = qrCodeViewModel
- private val contentPadding = 25
- private var textWidth = contentTextPaint.measureText(qrCodeViewModel.qrContent).toInt()
-
- override fun draw(canvas: Canvas) {
- canvas.drawRect(qrCodeViewModel.boundingRect, boundingRectPaint)
- canvas.drawRect(
- Rect(
- qrCodeViewModel.boundingRect.left,
- qrCodeViewModel.boundingRect.bottom + contentPadding/2,
- qrCodeViewModel.boundingRect.left + textWidth + contentPadding*2,
- qrCodeViewModel.boundingRect.bottom + contentTextPaint.textSize.toInt() + contentPadding),
- contentRectPaint
- )
- canvas.drawText(
- qrCodeViewModel.qrContent,
- (qrCodeViewModel.boundingRect.left + contentPadding).toFloat(),
- (qrCodeViewModel.boundingRect.bottom + contentPadding*2).toFloat(),
- contentTextPaint
- )
- }
-
- override fun setAlpha(alpha: Int) {
- boundingRectPaint.alpha = alpha
- contentRectPaint.alpha = alpha
- contentTextPaint.alpha = alpha
- }
-
- override fun setColorFilter(colorFiter: ColorFilter?) {
- boundingRectPaint.colorFilter = colorFilter
- contentRectPaint.colorFilter = colorFilter
- contentTextPaint.colorFilter = colorFilter
- }
-
- @Deprecated("Deprecated in Java")
- override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
-}
\ No newline at end of file
diff --git a/CameraX-MLKit/app/src/main/java/com/example/camerax_mlkit/TextViewModel.kt b/CameraX-MLKit/app/src/main/java/com/example/camerax_mlkit/TextViewModel.kt
new file mode 100644
index 00000000..91fc2a00
--- /dev/null
+++ b/CameraX-MLKit/app/src/main/java/com/example/camerax_mlkit/TextViewModel.kt
@@ -0,0 +1,19 @@
+package com.example.camerax_mlkit
+
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.View
+import com.google.mlkit.vision.text.Text
+
+class TextViewModel(line: Text.Line) {
+ var boundingRect: Rect? = line.boundingBox
+ var lineContent: String = ""
+ var lineTouchCallback = { v: View, e: MotionEvent -> false }
+
+ init {
+ lineContent = line.text
+ lineTouchCallback = { v: View, e: MotionEvent ->
+ true // return true from the callback to signify the event was handled
+ }
+ }
+}
\ No newline at end of file
diff --git a/CameraX-MLKit/app/src/main/java/com/example/camerax_mlkit/utils/BuildRect.kt b/CameraX-MLKit/app/src/main/java/com/example/camerax_mlkit/utils/BuildRect.kt
new file mode 100644
index 00000000..23b310ff
--- /dev/null
+++ b/CameraX-MLKit/app/src/main/java/com/example/camerax_mlkit/utils/BuildRect.kt
@@ -0,0 +1,65 @@
+package com.example.camerax_mlkit.utils
+
+import android.graphics.*
+import android.graphics.drawable.Drawable
+
+class BuildRect(private val boundingRect: Rect?, private val content: String) : Drawable() {
+
+ private val boundingRectPaint = Paint().apply {
+ style = Paint.Style.STROKE
+ color = Color.YELLOW
+ strokeWidth = 5F
+ alpha = 200
+ }
+
+ private val contentRectPaint = Paint().apply {
+ style = Paint.Style.FILL
+ color = Color.YELLOW
+ alpha = 255
+ }
+
+ private val contentTextPaint = Paint().apply {
+ color = Color.DKGRAY
+ alpha = 255
+ textSize = 36F
+ }
+
+ private val contentPadding = 25
+ private var textWidth = contentTextPaint.measureText(content).toInt()
+
+ override fun draw(canvas: Canvas) {
+ boundingRect?.let { rect ->
+ canvas.drawRect(rect, boundingRectPaint)
+ canvas.drawRect(
+ Rect(
+ rect.left,
+ rect.bottom + contentPadding / 2,
+ rect.left + textWidth + contentPadding * 2,
+ rect.bottom + contentTextPaint.textSize.toInt() + contentPadding
+ ),
+ contentRectPaint
+ )
+ canvas.drawText(
+ content,
+ (rect.left + contentPadding).toFloat(),
+ (rect.bottom + contentPadding * 2).toFloat(),
+ contentTextPaint
+ )
+ }
+ }
+
+ override fun setAlpha(alpha: Int) {
+ boundingRectPaint.alpha = alpha
+ contentRectPaint.alpha = alpha
+ contentTextPaint.alpha = alpha
+ }
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ boundingRectPaint.colorFilter = colorFilter
+ contentRectPaint.colorFilter = colorFilter
+ contentTextPaint.colorFilter = colorFilter
+ }
+
+ @Deprecated("Deprecated in Java", ReplaceWith("PixelFormat.TRANSLUCENT", "android.graphics.PixelFormat"))
+ override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
+}
\ No newline at end of file
diff --git a/CameraX-MLKit/app/src/main/res/layout/activity_main.xml b/CameraX-MLKit/app/src/main/res/layout/activity_main.xml
index 90d64126..94236507 100644
--- a/CameraX-MLKit/app/src/main/res/layout/activity_main.xml
+++ b/CameraX-MLKit/app/src/main/res/layout/activity_main.xml
@@ -20,6 +20,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:id="@+id/myConstraintLayout"
tools:context=".MainActivity">