Migrate to CameraX
Since the app is on minSDK 21 now, and because some camera drivers apparently don't support the torch feature through the legacy API.
This commit is contained in:
+4
-1
@@ -76,7 +76,10 @@ dependencies {
|
||||
implementation "androidx.preference:preference:1.2.1"
|
||||
|
||||
// External
|
||||
implementation 'com.github.markusfisch:CameraView:1.10.0'
|
||||
implementation 'androidx.camera:camera-core:1.4.2'
|
||||
implementation 'androidx.camera:camera-camera2:1.4.2'
|
||||
implementation 'androidx.camera:camera-lifecycle:1.4.2'
|
||||
implementation 'androidx.camera:camera-view:1.4.2'
|
||||
implementation 'com.github.markusfisch:ScalingImageView:1.4.3'
|
||||
implementation 'com.github.markusfisch:zxing-cpp:v3.0.2.1'
|
||||
|
||||
|
||||
@@ -8,20 +8,31 @@ import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Rect
|
||||
import android.hardware.Camera
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.SeekBar
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.camera.core.Camera
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.FocusMeteringAction
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.ImageProxy
|
||||
import androidx.camera.core.Preview
|
||||
import androidx.camera.core.TorchState
|
||||
import androidx.camera.core.resolutionselector.AspectRatioStrategy
|
||||
import androidx.camera.core.resolutionselector.ResolutionSelector
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.adapter.prettifyFormatName
|
||||
import de.markusfisch.android.binaryeye.app.PERMISSION_CAMERA
|
||||
@@ -52,7 +63,6 @@ import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.widget.DetectorView
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
import de.markusfisch.android.binaryeye.zxingcpp.migrateBarcodeFormatName
|
||||
import de.markusfisch.android.cameraview.widget.CameraView
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.Binarizer
|
||||
@@ -61,19 +71,35 @@ import de.markusfisch.android.zxingcpp.ZxingCpp.Result
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.TextMode
|
||||
import java.io.FileInputStream
|
||||
import java.util.Scanner
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class CameraActivity : AppCompatActivity() {
|
||||
private val frameRoi = Rect()
|
||||
private val previewRect = Rect()
|
||||
private val viewBounds = Rect()
|
||||
private val matrix = Matrix()
|
||||
private val analyzerExecutor: ExecutorService =
|
||||
Executors.newSingleThreadExecutor()
|
||||
private val readerOptions = ReaderOptions(
|
||||
tryHarder = prefs.tryHarder,
|
||||
tryRotate = prefs.autoRotate,
|
||||
tryInvert = true,
|
||||
tryDownscale = true,
|
||||
maxNumberOfSymbols = 1,
|
||||
textMode = TextMode.PLAIN
|
||||
)
|
||||
|
||||
private lateinit var cameraView: CameraView
|
||||
private lateinit var cameraView: PreviewView
|
||||
private lateinit var detectorView: DetectorView
|
||||
private lateinit var zoomBar: SeekBar
|
||||
private lateinit var flashFab: FloatingActionButton
|
||||
|
||||
private var cameraProvider: ProcessCameraProvider? = null
|
||||
private var camera: Camera? = null
|
||||
private var currentProfile = prefs.profile
|
||||
private var shouldStoreSettings = true
|
||||
private var formatsToRead = setOf<BarcodeFormat>()
|
||||
@@ -87,8 +113,8 @@ class CameraActivity : AppCompatActivity() {
|
||||
private var restrictFormat: String? = null
|
||||
private var searchTerm: Regex? = null
|
||||
private var ignoreNext: String? = null
|
||||
private var fallbackBuffer: IntArray? = null
|
||||
private var requestCameraPermission = true
|
||||
private var useLocalAverage = false
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
@@ -101,10 +127,14 @@ class CameraActivity : AppCompatActivity() {
|
||||
grantResults
|
||||
)
|
||||
when (requestCode) {
|
||||
PERMISSION_CAMERA -> if (grantResults.isNotEmpty() &&
|
||||
grantResults[0] != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
toast(R.string.camera_error)
|
||||
PERMISSION_CAMERA -> {
|
||||
if (grantResults.isNotEmpty() &&
|
||||
grantResults[0] != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
toast(R.string.camera_error)
|
||||
} else if (grantResults.isNotEmpty()) {
|
||||
bindCameraUseCases()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,12 +172,12 @@ class CameraActivity : AppCompatActivity() {
|
||||
|
||||
initBars()
|
||||
|
||||
cameraView = findViewById(R.id.camera_view) as CameraView
|
||||
detectorView = findViewById(R.id.detector_view) as DetectorView
|
||||
zoomBar = findViewById(R.id.zoom) as SeekBar
|
||||
flashFab = findViewById(R.id.flash) as FloatingActionButton
|
||||
cameraView = findViewById(R.id.camera_view)
|
||||
detectorView = findViewById(R.id.detector_view)
|
||||
zoomBar = findViewById(R.id.zoom)
|
||||
flashFab = findViewById(R.id.flash)
|
||||
|
||||
initCameraView()
|
||||
initPreviewView()
|
||||
initZoomBar()
|
||||
initDetectorView()
|
||||
|
||||
@@ -160,7 +190,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
fallbackBuffer = null
|
||||
analyzerExecutor.shutdown()
|
||||
releaseToneGenerators()
|
||||
}
|
||||
|
||||
@@ -180,7 +210,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
// for this session. Otherwise ActivityCompat.requestPermissions()
|
||||
// will trigger onResume() again and again.
|
||||
if (hasCameraPermission(requestCameraPermission)) {
|
||||
openCamera()
|
||||
bindCameraUseCases()
|
||||
}
|
||||
requestCameraPermission = false
|
||||
}
|
||||
@@ -195,7 +225,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
val packageInfo = packageManager.getPackageInfo(packageName, 0)
|
||||
val installedSince = System.currentTimeMillis() -
|
||||
packageInfo.firstInstallTime
|
||||
packageInfo.firstInstallTime
|
||||
return installedSince < 86400000
|
||||
}
|
||||
|
||||
@@ -204,7 +234,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
return false
|
||||
}
|
||||
shouldStoreSettings = false
|
||||
zoomBar.max = 0 // Reset zoom when there are no preferences yet.
|
||||
zoomBar.progress = 10
|
||||
currentProfile = prefs.profile
|
||||
recreate()
|
||||
return true
|
||||
@@ -257,30 +287,19 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun openCamera() {
|
||||
cameraView.openAsync(
|
||||
CameraView.findCameraId(
|
||||
@Suppress("DEPRECATION")
|
||||
if (frontFacing) {
|
||||
Camera.CameraInfo.CAMERA_FACING_FRONT
|
||||
} else {
|
||||
Camera.CameraInfo.CAMERA_FACING_BACK
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
closeCamera()
|
||||
unbindCameraUseCases()
|
||||
if (shouldStoreSettings) {
|
||||
storeSettings()
|
||||
}
|
||||
shouldStoreSettings = true
|
||||
}
|
||||
|
||||
private fun closeCamera() {
|
||||
cameraView.close()
|
||||
private fun unbindCameraUseCases() {
|
||||
decoding = false
|
||||
cameraProvider?.unbindAll()
|
||||
camera = null
|
||||
}
|
||||
|
||||
private fun storeSettings() {
|
||||
@@ -290,7 +309,6 @@ class CameraActivity : AppCompatActivity() {
|
||||
|
||||
override fun onRestoreInstanceState(savedState: Bundle) {
|
||||
super.onRestoreInstanceState(savedState)
|
||||
zoomBar.max = savedState.getInt(ZOOM_MAX)
|
||||
zoomBar.progress = savedState.getInt(ZOOM_LEVEL)
|
||||
frontFacing = savedState.getBoolean(FRONT_FACING)
|
||||
bulkMode = savedState.getBoolean(BULK_MODE)
|
||||
@@ -299,7 +317,6 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putInt(ZOOM_MAX, zoomBar.max)
|
||||
outState.putInt(ZOOM_LEVEL, zoomBar.progress)
|
||||
outState.putBoolean(FRONT_FACING, frontFacing)
|
||||
outState.putBoolean(BULK_MODE, bulkMode)
|
||||
@@ -389,7 +406,9 @@ class CameraActivity : AppCompatActivity() {
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
else -> {
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,9 +417,8 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun switchCamera() {
|
||||
closeCamera()
|
||||
frontFacing = frontFacing xor true
|
||||
openCamera()
|
||||
bindCameraUseCases()
|
||||
}
|
||||
|
||||
private fun showRestrictionDialog() {
|
||||
@@ -466,8 +484,13 @@ class CameraActivity : AppCompatActivity() {
|
||||
.setTitle(R.string.profile)
|
||||
.setItems(profiles) { _, which ->
|
||||
val newProfile = when (which) {
|
||||
0 -> null
|
||||
else -> profiles[which]
|
||||
0 -> {
|
||||
null
|
||||
}
|
||||
|
||||
else -> {
|
||||
profiles[which]
|
||||
}
|
||||
}
|
||||
if (currentProfile != newProfile) {
|
||||
storeSettings()
|
||||
@@ -486,8 +509,8 @@ class CameraActivity : AppCompatActivity() {
|
||||
startActivity(EncodeActivity.newIntent(this, text).apply {
|
||||
addFlags(
|
||||
Intent.FLAG_ACTIVITY_NO_HISTORY or
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TASK or
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TASK or
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
)
|
||||
})
|
||||
finish()
|
||||
@@ -505,28 +528,29 @@ class CameraActivity : AppCompatActivity() {
|
||||
if (file != null) {
|
||||
val fs = FileInputStream(file.fileDescriptor)
|
||||
val scn = Scanner(fs).useDelimiter("\\A")
|
||||
if (scn.hasNext()) {
|
||||
startActivity(
|
||||
EncodeActivity.newIntent(this, scn.next()).apply {
|
||||
addFlags(
|
||||
Intent.FLAG_ACTIVITY_NO_HISTORY or
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TASK or
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
)
|
||||
}
|
||||
)
|
||||
finish()
|
||||
}
|
||||
if (scn.hasNext()) {
|
||||
startActivity(
|
||||
EncodeActivity.newIntent(this, scn.next()).apply {
|
||||
addFlags(
|
||||
Intent.FLAG_ACTIVITY_NO_HISTORY or
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TASK or
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
)
|
||||
}
|
||||
)
|
||||
finish()
|
||||
}
|
||||
file.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initCameraView() {
|
||||
cameraView.setUseOrientationListener(true)
|
||||
private fun initPreviewView() {
|
||||
cameraView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
||||
updateFrameRoiAndMappingMatrix()
|
||||
}
|
||||
@Suppress("ClickableViewAccessibility")
|
||||
cameraView.setOnTouchListener(object : View.OnTouchListener {
|
||||
var focus = true
|
||||
var offset = -1f
|
||||
var progress = 0
|
||||
|
||||
@@ -546,7 +570,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
val dist = offset - pos
|
||||
val maxValue = zoomBar.max
|
||||
val change = maxValue /
|
||||
v.height.toFloat() * 2f * dist
|
||||
v.height.toFloat() * 2f * dist
|
||||
zoomBar.progress = min(
|
||||
maxValue,
|
||||
max(progress + change.roundToInt(), 0)
|
||||
@@ -556,131 +580,25 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP -> {
|
||||
// Stop calling focusTo() as soon as it returns false
|
||||
// to avoid throwing and catching future exceptions.
|
||||
if (focus) {
|
||||
focus = cameraView.focusTo(v, event.x, event.y)
|
||||
if (focus) {
|
||||
v?.performClick()
|
||||
return true
|
||||
}
|
||||
val pointFactory = cameraView.meteringPointFactory
|
||||
val point = pointFactory.createPoint(event.x, event.y)
|
||||
val action = FocusMeteringAction.Builder(point).build()
|
||||
camera?.cameraControl?.startFocusAndMetering(action)
|
||||
if (v != null) {
|
||||
v.performClick()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
@Suppress("DEPRECATION")
|
||||
cameraView.setOnCameraListener(object : CameraView.OnCameraListener {
|
||||
override fun onConfigureParameters(
|
||||
parameters: Camera.Parameters
|
||||
) {
|
||||
zoomBar.visibility = if (parameters.isZoomSupported) {
|
||||
restoreZoomBarSettings()
|
||||
val max = parameters.maxZoom
|
||||
if (zoomBar.max != max) {
|
||||
zoomBar.max = max
|
||||
zoomBar.progress = max / 10
|
||||
storeZoomBarSettings()
|
||||
}
|
||||
parameters.zoom = zoomBar.progress
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
val sceneModes = parameters.supportedSceneModes
|
||||
sceneModes?.let {
|
||||
for (mode in sceneModes) {
|
||||
if (mode == Camera.Parameters.SCENE_MODE_BARCODE) {
|
||||
parameters.sceneMode = mode
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
CameraView.setAutoFocus(parameters)
|
||||
updateFlashFab(parameters.flashMode == null)
|
||||
}
|
||||
|
||||
override fun onCameraError() {
|
||||
this@CameraActivity.toast(R.string.camera_error)
|
||||
}
|
||||
|
||||
override fun onCameraReady(camera: Camera) {
|
||||
frameMetrics = FrameMetrics(
|
||||
cameraView.frameWidth,
|
||||
cameraView.frameHeight,
|
||||
cameraView.frameOrientation
|
||||
)
|
||||
updateFrameRoiAndMappingMatrix()
|
||||
ignoreNext = null
|
||||
decoding = true
|
||||
// These settings can't change while the camera is open.
|
||||
val options = ReaderOptions(
|
||||
tryHarder = prefs.tryHarder,
|
||||
tryRotate = prefs.autoRotate,
|
||||
tryInvert = true,
|
||||
tryDownscale = true,
|
||||
maxNumberOfSymbols = 1,
|
||||
textMode = TextMode.PLAIN
|
||||
)
|
||||
var useLocalAverage = false
|
||||
camera.setPreviewCallback { frameData, _ ->
|
||||
if (!decoding ||
|
||||
frameData == null ||
|
||||
frameData.isEmpty()
|
||||
) {
|
||||
return@setPreviewCallback
|
||||
}
|
||||
useLocalAverage = useLocalAverage xor true
|
||||
ZxingCpp.readByteArray(
|
||||
frameData,
|
||||
frameMetrics.width,
|
||||
frameRoi.left, frameRoi.top,
|
||||
frameRoi.width(), frameRoi.height(),
|
||||
frameMetrics.orientation,
|
||||
options.apply {
|
||||
// By default, ZXing uses LOCAL_AVERAGE, but
|
||||
// this does not work well with inverted
|
||||
// barcodes on low-contrast backgrounds.
|
||||
binarizer = if (useLocalAverage) {
|
||||
Binarizer.LOCAL_AVERAGE
|
||||
} else {
|
||||
Binarizer.GLOBAL_HISTOGRAM
|
||||
}
|
||||
formats = formatsToRead
|
||||
}
|
||||
)?.let { results ->
|
||||
val result = results.first()
|
||||
val text = result.text
|
||||
if (text == ignoreNext) {
|
||||
return@let
|
||||
}
|
||||
val term = searchTerm
|
||||
if (term != null &&
|
||||
!text.matches(term) &&
|
||||
!text.contains(term)
|
||||
) {
|
||||
ignoreNext = text
|
||||
errorFeedback()
|
||||
toast(R.string.does_not_match_search_term)
|
||||
return@let
|
||||
}
|
||||
postResult(result)
|
||||
decoding = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreviewStarted(camera: Camera) {
|
||||
}
|
||||
|
||||
override fun onCameraStopping(camera: Camera) {
|
||||
camera.setPreviewCallback(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun initZoomBar() {
|
||||
zoomBar.max = 100
|
||||
zoomBar.progress = 10
|
||||
restoreZoomBarSettings()
|
||||
zoomBar.setOnSeekBarChangeListener(object :
|
||||
SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(
|
||||
@@ -688,35 +606,25 @@ class CameraActivity : AppCompatActivity() {
|
||||
progress: Int,
|
||||
fromUser: Boolean
|
||||
) {
|
||||
cameraView.camera?.setZoom(progress)
|
||||
camera?.cameraControl?.setLinearZoom(progress / 100f)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
||||
}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||
storeZoomBarSettings()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun Camera.setZoom(zoom: Int) {
|
||||
try {
|
||||
val params = parameters
|
||||
params.zoom = zoom
|
||||
parameters = params
|
||||
} catch (_: RuntimeException) {
|
||||
// Ignore. There's nothing we can do.
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeZoomBarSettings() {
|
||||
prefs.preferences.edit {
|
||||
putInt(ZOOM_MAX, zoomBar.max)
|
||||
putInt(ZOOM_LEVEL, zoomBar.progress)
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreZoomBarSettings() {
|
||||
zoomBar.max = prefs.preferences.getInt(ZOOM_MAX, zoomBar.max)
|
||||
zoomBar.progress = prefs.preferences.getInt(
|
||||
ZOOM_LEVEL,
|
||||
zoomBar.progress
|
||||
@@ -735,24 +643,160 @@ class CameraActivity : AppCompatActivity() {
|
||||
detectorView.cropHandleName = "camera_crop_handle"
|
||||
}
|
||||
|
||||
private fun bindCameraUseCases() {
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||
cameraProviderFuture.addListener({
|
||||
if (isDestroyed) {
|
||||
return@addListener
|
||||
}
|
||||
val provider = try {
|
||||
cameraProviderFuture.get()
|
||||
} catch (_: Exception) {
|
||||
toast(R.string.camera_error)
|
||||
return@addListener
|
||||
}
|
||||
cameraProvider = provider
|
||||
val resolutionSelector = ResolutionSelector.Builder()
|
||||
.setAspectRatioStrategy(
|
||||
AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY
|
||||
)
|
||||
.build()
|
||||
val preview = Preview.Builder()
|
||||
.setResolutionSelector(resolutionSelector)
|
||||
.build()
|
||||
val imageAnalysis = ImageAnalysis.Builder()
|
||||
.setResolutionSelector(resolutionSelector)
|
||||
.setBackpressureStrategy(
|
||||
ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
|
||||
)
|
||||
.build()
|
||||
imageAnalysis.setAnalyzer(analyzerExecutor) { image ->
|
||||
analyzeImage(image)
|
||||
}
|
||||
val cameraSelector = CameraSelector.Builder()
|
||||
.requireLensFacing(
|
||||
if (frontFacing) {
|
||||
CameraSelector.LENS_FACING_FRONT
|
||||
} else {
|
||||
CameraSelector.LENS_FACING_BACK
|
||||
}
|
||||
)
|
||||
.build()
|
||||
try {
|
||||
provider.unbindAll()
|
||||
camera = provider.bindToLifecycle(
|
||||
this,
|
||||
cameraSelector,
|
||||
preview,
|
||||
imageAnalysis
|
||||
)
|
||||
preview.surfaceProvider = cameraView.surfaceProvider
|
||||
ignoreNext = null
|
||||
decoding = true
|
||||
useLocalAverage = false
|
||||
updateZoomState()
|
||||
updateFlashFab(!(camera?.cameraInfo?.hasFlashUnit() ?: false))
|
||||
updateFrameRoiAndMappingMatrix()
|
||||
} catch (_: Exception) {
|
||||
camera = null
|
||||
toast(R.string.camera_error)
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(this))
|
||||
}
|
||||
|
||||
private fun analyzeImage(image: ImageProxy) {
|
||||
try {
|
||||
if (!decoding) {
|
||||
return
|
||||
}
|
||||
frameMetrics = FrameMetrics(
|
||||
image.width,
|
||||
image.height,
|
||||
image.imageInfo.rotationDegrees
|
||||
)
|
||||
updateFrameRoiAndMappingMatrix()
|
||||
if (frameRoi.width() < 1 || frameRoi.height() < 1) {
|
||||
return
|
||||
}
|
||||
useLocalAverage = useLocalAverage xor true
|
||||
val yPlane = image.planes[0]
|
||||
ZxingCpp.readYBuffer(
|
||||
yPlane.buffer,
|
||||
yPlane.rowStride,
|
||||
frameRoi,
|
||||
frameMetrics.orientation,
|
||||
readerOptions.apply {
|
||||
binarizer = if (useLocalAverage) {
|
||||
Binarizer.LOCAL_AVERAGE
|
||||
} else {
|
||||
Binarizer.GLOBAL_HISTOGRAM
|
||||
}
|
||||
formats = formatsToRead
|
||||
tryHarder = prefs.tryHarder
|
||||
tryRotate = prefs.autoRotate
|
||||
}
|
||||
)?.firstOrNull()?.let { result ->
|
||||
handleDecodeResult(result)
|
||||
}
|
||||
} finally {
|
||||
image.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDecodeResult(result: Result) {
|
||||
val text = result.text
|
||||
if (text == ignoreNext) {
|
||||
return
|
||||
}
|
||||
val term = searchTerm
|
||||
if (term != null &&
|
||||
!text.matches(term) &&
|
||||
!text.contains(term)
|
||||
) {
|
||||
ignoreNext = text
|
||||
cameraView.post {
|
||||
errorFeedback()
|
||||
toast(R.string.does_not_match_search_term)
|
||||
}
|
||||
return
|
||||
}
|
||||
decoding = false
|
||||
postResult(result)
|
||||
}
|
||||
|
||||
private fun updateZoomState() {
|
||||
zoomBar.visibility = if (
|
||||
camera?.cameraInfo?.zoomState?.value != null
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFrameRoiAndMappingMatrix() {
|
||||
val viewRect = cameraView.previewRect
|
||||
if (viewRect.width() < 1 ||
|
||||
viewRect.height() < 1 ||
|
||||
viewBounds.set(0, 0, cameraView.width, cameraView.height)
|
||||
if (viewBounds.width() < 1 ||
|
||||
viewBounds.height() < 1 ||
|
||||
!frameMetrics.isValid()
|
||||
) {
|
||||
return
|
||||
}
|
||||
previewRect.setCovered(
|
||||
viewBounds.width(),
|
||||
viewBounds.height(),
|
||||
frameMetrics
|
||||
)
|
||||
val viewRoi = if (
|
||||
detectorView.roi.width() < 1 ||
|
||||
detectorView.roi.height() < 1
|
||||
) {
|
||||
viewRect
|
||||
viewBounds
|
||||
} else {
|
||||
detectorView.roi
|
||||
}
|
||||
frameRoi.setFrameRoi(frameMetrics, viewRect, viewRoi)
|
||||
matrix.setFrameToView(frameMetrics, viewRect, viewRoi)
|
||||
frameRoi.setFrameRoi(frameMetrics, previewRect, viewRoi)
|
||||
matrix.setFrameToView(frameMetrics, previewRect, viewRoi)
|
||||
}
|
||||
|
||||
private fun updateFlashFab(unavailable: Boolean) {
|
||||
@@ -765,22 +809,54 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun toggleTorchMode() {
|
||||
val camera = cameraView.camera ?: return
|
||||
val parameters = camera.parameters ?: return
|
||||
parameters.flashMode = if (
|
||||
parameters.flashMode != Camera.Parameters.FLASH_MODE_OFF
|
||||
val currentState = camera?.cameraInfo?.torchState?.value ?: return
|
||||
camera?.cameraControl?.enableTorch(
|
||||
currentState != TorchState.ON
|
||||
)
|
||||
}
|
||||
|
||||
private fun Rect.setCovered(
|
||||
viewWidth: Int,
|
||||
viewHeight: Int,
|
||||
frameMetrics: FrameMetrics
|
||||
) {
|
||||
val frameWidth: Int
|
||||
val frameHeight: Int
|
||||
when (frameMetrics.orientation) {
|
||||
90, 270 -> {
|
||||
frameWidth = frameMetrics.height
|
||||
frameHeight = frameMetrics.width
|
||||
}
|
||||
|
||||
else -> {
|
||||
frameWidth = frameMetrics.width
|
||||
frameHeight = frameMetrics.height
|
||||
}
|
||||
}
|
||||
if (frameWidth < 1 || frameHeight < 1) {
|
||||
set(0, 0, 0, 0)
|
||||
return
|
||||
}
|
||||
var coveredWidth = frameWidth
|
||||
var coveredHeight = frameHeight
|
||||
if (viewWidth.toLong() * coveredWidth <
|
||||
viewHeight.toLong() * coveredHeight
|
||||
) {
|
||||
Camera.Parameters.FLASH_MODE_OFF
|
||||
coveredWidth = coveredWidth * viewHeight / coveredHeight
|
||||
coveredHeight = viewHeight
|
||||
} else {
|
||||
Camera.Parameters.FLASH_MODE_TORCH
|
||||
}
|
||||
try {
|
||||
camera.parameters = parameters
|
||||
} catch (e: RuntimeException) {
|
||||
toast(e.message ?: getString(R.string.error_flash))
|
||||
coveredHeight = coveredHeight * viewWidth / coveredWidth
|
||||
coveredWidth = viewWidth
|
||||
}
|
||||
val left = (viewWidth - coveredWidth) / 2
|
||||
val top = (viewHeight - coveredHeight) / 2
|
||||
set(
|
||||
left,
|
||||
top,
|
||||
left + coveredWidth,
|
||||
top + coveredHeight
|
||||
)
|
||||
}
|
||||
|
||||
private fun postResult(result: Result) {
|
||||
@@ -808,9 +884,11 @@ class CameraActivity : AppCompatActivity() {
|
||||
finish()
|
||||
}
|
||||
|
||||
returnUri != null -> execShareIntent(
|
||||
Intent(Intent.ACTION_VIEW, returnUri)
|
||||
)
|
||||
returnUri != null -> {
|
||||
execShareIntent(
|
||||
Intent(Intent.ACTION_VIEW, returnUri)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
showResult(result, bulkMode)
|
||||
@@ -825,10 +903,12 @@ class CameraActivity : AppCompatActivity() {
|
||||
if (bulkMode) {
|
||||
when (prefs.ignoreDuplicates()) {
|
||||
Preferences.Companion.IgnoreDuplicates.Consecutive,
|
||||
Preferences.Companion.IgnoreDuplicates.Any ->
|
||||
Preferences.Companion.IgnoreDuplicates.Any -> {
|
||||
ignoreNext = result.text
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
if (prefs.showToastInBulkMode) {
|
||||
toast(result.text)
|
||||
@@ -842,7 +922,6 @@ class CameraActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
private const val PICK_FILE_RESULT_CODE = 1
|
||||
private const val ZOOM_MAX = "zoom_max"
|
||||
private const val ZOOM_LEVEL = "zoom_level"
|
||||
private const val FRONT_FACING = "front_facing"
|
||||
private const val BULK_MODE = "bulk_mode"
|
||||
@@ -906,7 +985,9 @@ fun Activity.showResult(
|
||||
R.string.bluetooth_send_fail
|
||||
}
|
||||
|
||||
else -> R.string.bluetooth_send_success
|
||||
else -> {
|
||||
R.string.bluetooth_send_success
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<de.markusfisch.android.cameraview.widget.CameraView
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<androidx.camera.view.PreviewView
|
||||
android:id="@+id/camera_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
android:layout_height="match_parent"
|
||||
app:scaleType="fillCenter"/>
|
||||
<de.markusfisch.android.binaryeye.widget.DetectorView
|
||||
android:id="@+id/detector_view"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
Reference in New Issue
Block a user