kopia lustrzana https://github.com/ryukoposting/Signal-Android
Fix QR scanning bug when using camerax.
rodzic
499cdd9f29
commit
e83cb6fa8b
|
@ -0,0 +1,68 @@
|
||||||
|
package org.signal.qr
|
||||||
|
|
||||||
|
import android.graphics.ImageFormat
|
||||||
|
import androidx.camera.core.ImageProxy
|
||||||
|
import com.google.zxing.LuminanceSource
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Luminance source that gets data via an [ImageProxy]. The main reason for this is because
|
||||||
|
* the Y-Plane provided by the camera framework can have a row stride (number of bytes that make up a row)
|
||||||
|
* that is different than the image width.
|
||||||
|
*
|
||||||
|
* An image width can be reported as 1080 but the row stride may be 1088. Thus when representing a row-major
|
||||||
|
* 2D array as a 1D array, the math can go sideways if width is used instead of row stride.
|
||||||
|
*/
|
||||||
|
class ImageProxyLuminanceSource(image: ImageProxy) : LuminanceSource(image.width, image.height) {
|
||||||
|
|
||||||
|
val yData: ByteArray
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(image.format == ImageFormat.YUV_420_888) { "Invalid image format" }
|
||||||
|
|
||||||
|
yData = ByteArray(image.width * image.height)
|
||||||
|
|
||||||
|
val yBuffer: ByteBuffer = image.planes[0].buffer
|
||||||
|
yBuffer.position(0)
|
||||||
|
|
||||||
|
val yRowStride: Int = image.planes[0].rowStride
|
||||||
|
|
||||||
|
for (y in 0 until image.height) {
|
||||||
|
val yIndex: Int = y * yRowStride
|
||||||
|
yBuffer.position(yIndex)
|
||||||
|
yBuffer.get(yData, y * image.width, image.width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRow(y: Int, row: ByteArray?): ByteArray {
|
||||||
|
require(y in 0 until height) { "Requested row is outside the image: $y" }
|
||||||
|
|
||||||
|
val toReturn: ByteArray = if (row == null || row.size < width) {
|
||||||
|
ByteArray(width)
|
||||||
|
} else {
|
||||||
|
row
|
||||||
|
}
|
||||||
|
|
||||||
|
val yIndex: Int = y * width
|
||||||
|
|
||||||
|
yData.copyInto(toReturn, 0, yIndex, yIndex + width)
|
||||||
|
|
||||||
|
return toReturn
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMatrix(): ByteArray {
|
||||||
|
return yData
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(): IntArray {
|
||||||
|
val argbArray = IntArray(width * height)
|
||||||
|
|
||||||
|
var yValue: Int
|
||||||
|
yData.forEachIndexed { i, byte ->
|
||||||
|
yValue = (byte.toInt() and 0xff).coerceIn(0..255)
|
||||||
|
argbArray[i] = 255 shl 24 or (yValue and 255 shl 16) or (yValue and 255 shl 8) or (yValue and 255)
|
||||||
|
}
|
||||||
|
|
||||||
|
return argbArray
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
package org.signal.qr
|
package org.signal.qr
|
||||||
|
|
||||||
|
import androidx.camera.core.ImageProxy
|
||||||
import com.google.zxing.BinaryBitmap
|
import com.google.zxing.BinaryBitmap
|
||||||
import com.google.zxing.ChecksumException
|
import com.google.zxing.ChecksumException
|
||||||
import com.google.zxing.DecodeHintType
|
import com.google.zxing.DecodeHintType
|
||||||
import com.google.zxing.FormatException
|
import com.google.zxing.FormatException
|
||||||
|
import com.google.zxing.LuminanceSource
|
||||||
import com.google.zxing.NotFoundException
|
import com.google.zxing.NotFoundException
|
||||||
import com.google.zxing.PlanarYUVLuminanceSource
|
import com.google.zxing.PlanarYUVLuminanceSource
|
||||||
import com.google.zxing.Result
|
import com.google.zxing.Result
|
||||||
|
@ -21,19 +23,25 @@ class QrProcessor {
|
||||||
private var previousHeight = 0
|
private var previousHeight = 0
|
||||||
private var previousWidth = 0
|
private var previousWidth = 0
|
||||||
|
|
||||||
|
fun getScannedData(proxy: ImageProxy): String? {
|
||||||
|
return getScannedData(ImageProxyLuminanceSource(proxy))
|
||||||
|
}
|
||||||
|
|
||||||
fun getScannedData(
|
fun getScannedData(
|
||||||
data: ByteArray,
|
data: ByteArray,
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int
|
height: Int
|
||||||
): String? {
|
): String? {
|
||||||
try {
|
return getScannedData(PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false))
|
||||||
if (width != previousWidth || height != previousHeight) {
|
}
|
||||||
Log.i(TAG, "Processing $width x $height image, data: ${data.size}")
|
|
||||||
previousWidth = width
|
|
||||||
previousHeight = height
|
|
||||||
}
|
|
||||||
|
|
||||||
val source = PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false)
|
private fun getScannedData(source: LuminanceSource): String? {
|
||||||
|
try {
|
||||||
|
if (source.width != previousWidth || source.height != previousHeight) {
|
||||||
|
Log.i(TAG, "Processing ${source.width} x ${source.height} image")
|
||||||
|
previousWidth = source.width
|
||||||
|
previousHeight = source.height
|
||||||
|
}
|
||||||
|
|
||||||
val bitmap = BinaryBitmap(HybridBinarizer(source))
|
val bitmap = BinaryBitmap(HybridBinarizer(source))
|
||||||
val result: Result? = reader.decode(bitmap, mapOf(DecodeHintType.TRY_HARDER to true, DecodeHintType.CHARACTER_SET to "ISO-8859-1"))
|
val result: Result? = reader.decode(bitmap, mapOf(DecodeHintType.TRY_HARDER to true, DecodeHintType.CHARACTER_SET to "ISO-8859-1"))
|
||||||
|
|
|
@ -2,22 +2,30 @@ package org.signal.qr
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.ImageFormat
|
||||||
|
import android.util.Size
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.camera.core.AspectRatio
|
import androidx.camera.core.AspectRatio
|
||||||
import androidx.camera.core.Camera
|
import androidx.camera.core.Camera
|
||||||
import androidx.camera.core.CameraSelector
|
import androidx.camera.core.CameraSelector
|
||||||
import androidx.camera.core.ImageAnalysis
|
import androidx.camera.core.ImageAnalysis
|
||||||
|
import androidx.camera.core.ImageProxy
|
||||||
import androidx.camera.core.Preview
|
import androidx.camera.core.Preview
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.camera.view.PreviewView
|
import androidx.camera.view.PreviewView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.math.MathUtils.clamp
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.signal.qr.kitkat.ScanListener
|
import org.signal.qr.kitkat.ScanListener
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API21+ version of QR scanning view. Uses camerax APIs.
|
* API21+ version of QR scanning view. Uses camerax APIs.
|
||||||
*/
|
*/
|
||||||
|
@ -74,21 +82,17 @@ internal class ScannerView21 constructor(
|
||||||
val preview = Preview.Builder().build()
|
val preview = Preview.Builder().build()
|
||||||
|
|
||||||
val imageAnalysis = ImageAnalysis.Builder()
|
val imageAnalysis = ImageAnalysis.Builder()
|
||||||
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
|
.setTargetResolution(Size(1920, 1080))
|
||||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
imageAnalysis.setAnalyzer(analyzerExecutor) { proxy ->
|
imageAnalysis.setAnalyzer(analyzerExecutor) { proxy ->
|
||||||
val buffer = proxy.planes[0].buffer.apply { rewind() }
|
proxy.use {
|
||||||
val bytes = ByteArray(buffer.capacity())
|
val data: String? = qrProcessor.getScannedData(it)
|
||||||
buffer.get(bytes)
|
if (data != null) {
|
||||||
|
listener.onQrDataFound(data)
|
||||||
val data: String? = qrProcessor.getScannedData(bytes, proxy.width, proxy.height)
|
}
|
||||||
if (data != null) {
|
|
||||||
listener.onQrDataFound(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cameraProvider.unbindAll()
|
cameraProvider.unbindAll()
|
||||||
|
|
Ładowanie…
Reference in New Issue