This repository has been archived by the owner on Mar 8, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
task/camerax-example: Added camerax example of scanning a barcode.
This will server as a comparison between our lib and camerax.
- Loading branch information
1 parent
5dd2d1e
commit a0e9bd8
Showing
9 changed files
with
303 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
app/src/main/java/uk/co/brightec/kbarcode/app/camerax/BarcodeAnalyzer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package uk.co.brightec.kbarcode.app.camerax | ||
|
||
import android.content.Context | ||
import android.util.Log | ||
import androidx.annotation.OptIn | ||
import androidx.camera.core.ExperimentalGetImage | ||
import androidx.camera.core.ImageAnalysis | ||
import androidx.camera.core.ImageProxy | ||
import androidx.core.content.ContextCompat | ||
import com.google.android.gms.tasks.Tasks | ||
import com.google.mlkit.vision.barcode.Barcode | ||
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.common.InputImage | ||
import java.util.concurrent.ExecutionException | ||
|
||
internal class BarcodeAnalyzer( | ||
context: Context, | ||
private val barcodeListener: (List<Barcode>) -> Unit, | ||
) : ImageAnalysis.Analyzer { | ||
|
||
private val tag = BarcodeAnalyzer::class.simpleName ?: "BarcodeAnalyzer" | ||
private val mainExecutor = ContextCompat.getMainExecutor(context) | ||
private val scanner: BarcodeScanner = createScanner() | ||
|
||
@OptIn(ExperimentalGetImage::class) | ||
override fun analyze(imageProxy: ImageProxy) { | ||
val rotationDegrees = imageProxy.imageInfo.rotationDegrees | ||
val image = imageProxy.image | ||
if (image != null) { | ||
val inputImage = InputImage.fromMediaImage(image, rotationDegrees) | ||
val task = scanner.process(inputImage) | ||
try { | ||
val result = Tasks.await(task) | ||
if (result.isNotEmpty()) { | ||
mainExecutor.execute { | ||
barcodeListener.invoke(result) | ||
} | ||
} | ||
} catch (e: ExecutionException) { | ||
Log.e(tag, "Scanner process failed", e) | ||
} catch (e: InterruptedException) { | ||
Log.e(tag, "Scanner process interrupted", e) | ||
} | ||
} | ||
imageProxy.close() | ||
} | ||
|
||
private fun createScanner(): BarcodeScanner { | ||
val options = BarcodeScannerOptions.Builder() | ||
val formats = intArrayOf( | ||
Barcode.FORMAT_CODABAR, | ||
Barcode.FORMAT_EAN_13, | ||
Barcode.FORMAT_EAN_8, | ||
Barcode.FORMAT_ITF, | ||
Barcode.FORMAT_UPC_A, | ||
Barcode.FORMAT_UPC_E | ||
) | ||
if (formats.size > 1) { | ||
@Suppress("SpreadOperator") // Required by Google API | ||
options.setBarcodeFormats( | ||
formats[0], *formats.slice(IntRange(1, formats.size - 1)).toIntArray() | ||
) | ||
} else if (formats.size == 1) { | ||
options.setBarcodeFormats(formats[0]) | ||
} | ||
return BarcodeScanning.getClient(options.build()) | ||
} | ||
} |
181 changes: 181 additions & 0 deletions
181
app/src/main/java/uk/co/brightec/kbarcode/app/camerax/CameraXActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
package uk.co.brightec.kbarcode.app.camerax | ||
|
||
import android.Manifest | ||
import android.content.Context | ||
import android.content.DialogInterface | ||
import android.content.Intent | ||
import android.content.pm.PackageManager | ||
import android.os.Bundle | ||
import android.util.Log | ||
import android.view.MotionEvent | ||
import androidx.appcompat.app.AlertDialog | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.camera.core.Camera | ||
import androidx.camera.core.CameraControl | ||
import androidx.camera.core.CameraSelector | ||
import androidx.camera.core.FocusMeteringAction | ||
import androidx.camera.core.ImageAnalysis | ||
import androidx.camera.core.Preview | ||
import androidx.camera.lifecycle.ProcessCameraProvider | ||
import androidx.core.app.ActivityCompat | ||
import androidx.core.content.ContextCompat | ||
import kotlinx.android.synthetic.main.activity_camerax.* | ||
import kotlinx.android.synthetic.main.activity_camerax.text_barcodes | ||
import kotlinx.android.synthetic.main.activity_programmatic.* | ||
import uk.co.brightec.kbarcode.app.R | ||
import java.util.concurrent.ExecutionException | ||
import java.util.concurrent.ExecutorService | ||
import java.util.concurrent.Executors | ||
|
||
internal class CameraXActivity : | ||
AppCompatActivity(), ActivityCompat.OnRequestPermissionsResultCallback { | ||
|
||
private val tag = CameraXActivity::class.simpleName ?: "CameraXActivity" | ||
|
||
private lateinit var cameraExecutor: ExecutorService | ||
private lateinit var camera: Camera | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(R.layout.activity_camerax) | ||
setTitle(R.string.title_camerax) | ||
|
||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) | ||
!= PackageManager.PERMISSION_GRANTED | ||
) { | ||
requestCameraPermission() | ||
} else { | ||
startCamera() | ||
} | ||
|
||
cameraExecutor = Executors.newSingleThreadExecutor() | ||
} | ||
|
||
override fun onDestroy() { | ||
cameraExecutor.shutdown() | ||
super.onDestroy() | ||
} | ||
|
||
override fun onRequestPermissionsResult( | ||
requestCode: Int, | ||
permissions: Array<String>, | ||
grantResults: IntArray | ||
) { | ||
when (requestCode) { | ||
REQUEST_PERMISSION_CAMERA -> if ( | ||
grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED | ||
) { | ||
startCamera() | ||
} | ||
else -> | ||
super.onRequestPermissionsResult(requestCode, permissions, grantResults) | ||
} | ||
} | ||
|
||
private fun requestCameraPermission() { | ||
if (ActivityCompat.shouldShowRequestPermissionRationale( | ||
this, | ||
Manifest.permission.CAMERA | ||
) | ||
) { | ||
AlertDialog.Builder(this) | ||
.setTitle(R.string.title_camera_rationale) | ||
.setMessage(R.string.message_camera_rationale) | ||
.setPositiveButton(R.string.action_ok) { _: DialogInterface, _: Int -> | ||
ActivityCompat.requestPermissions( | ||
this, | ||
arrayOf(Manifest.permission.CAMERA), | ||
REQUEST_PERMISSION_CAMERA | ||
) | ||
} | ||
.show() | ||
} else { | ||
ActivityCompat.requestPermissions( | ||
this, | ||
arrayOf(Manifest.permission.CAMERA), | ||
REQUEST_PERMISSION_CAMERA | ||
) | ||
} | ||
} | ||
|
||
private fun startCamera() { | ||
val cameraProviderFuture = ProcessCameraProvider.getInstance(this) | ||
cameraProviderFuture.addListener( | ||
{ | ||
val previewUseCase = Preview.Builder() | ||
.build() | ||
.also { | ||
it.setSurfaceProvider(preview.surfaceProvider) | ||
} | ||
val imageAnalyzerUseCase = ImageAnalysis.Builder() | ||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) | ||
.build() | ||
.also { | ||
it.setAnalyzer( | ||
cameraExecutor, | ||
BarcodeAnalyzer(this) { barcodes -> | ||
val builder = StringBuilder() | ||
for (barcode in barcodes) { | ||
builder.append(barcode.displayValue).append("\n") | ||
} | ||
text_barcodes.text = builder.toString() | ||
} | ||
) | ||
} | ||
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() | ||
cameraProvider.unbindAll() | ||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA | ||
camera = cameraProvider.bindToLifecycle( | ||
this, cameraSelector, previewUseCase, imageAnalyzerUseCase | ||
) | ||
setupTapToFocus() | ||
}, | ||
ContextCompat.getMainExecutor(this) | ||
) | ||
} | ||
|
||
private fun setupTapToFocus() { | ||
preview.setOnTouchListener { view, event -> | ||
val actionMasked = event.actionMasked | ||
if (actionMasked == MotionEvent.ACTION_UP) { | ||
view.performClick() | ||
return@setOnTouchListener false | ||
} | ||
if (actionMasked != MotionEvent.ACTION_DOWN) { | ||
return@setOnTouchListener false | ||
} | ||
|
||
val cameraControl = camera.cameraControl | ||
val factory = preview.meteringPointFactory | ||
val point = factory.createPoint(event.x, event.y) | ||
val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF) | ||
.addPoint(point, FocusMeteringAction.FLAG_AE) | ||
.addPoint(point, FocusMeteringAction.FLAG_AWB) | ||
.build() | ||
val future = cameraControl.startFocusAndMetering(action) | ||
future.addListener( | ||
{ | ||
try { | ||
val result = future.get() | ||
Log.d(tag, "Focus Success: ${result.isFocusSuccessful}") | ||
} catch (e: CameraControl.OperationCanceledException) { | ||
Log.d(tag, "Focus cancelled") | ||
} catch (e: ExecutionException) { | ||
Log.e(tag, "Focus failed", e) | ||
} catch (e: InterruptedException) { | ||
Log.e(tag, "Focus interrupted", e) | ||
} | ||
}, | ||
cameraExecutor | ||
) | ||
true | ||
} | ||
} | ||
|
||
companion object { | ||
|
||
private const val REQUEST_PERMISSION_CAMERA = 1 | ||
|
||
fun getStartingIntent(context: Context) = Intent(context, CameraXActivity::class.java) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
xmlns:app="http://schemas.android.com/apk/res-auto" | ||
xmlns:tools="http://schemas.android.com/tools" | ||
android:layout_width="match_parent" | ||
android:layout_height="match_parent" | ||
android:keepScreenOn="true" | ||
tools:background="@color/black"> | ||
|
||
<androidx.camera.view.PreviewView | ||
android:id="@+id/preview" | ||
android:layout_width="match_parent" | ||
android:layout_height="match_parent" /> | ||
|
||
<TextView | ||
android:id="@+id/text_barcodes" | ||
android:layout_width="120dp" | ||
android:layout_height="wrap_content" | ||
android:background="@color/black" | ||
android:minLines="2" | ||
android:textColor="@color/white" | ||
app:layout_constraintBottom_toBottomOf="parent" | ||
app:layout_constraintStart_toStartOf="parent" /> | ||
</androidx.constraintlayout.widget.ConstraintLayout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters