Handle padded camera rows in QR scanner (#6012)
Camera HALs may expose a Y plane whose row stride is wider than the visible image. Pass that stride to ZXing so padded rows remain aligned, and cover the vendor-layout case with a valid VLESS QR regression test.
This commit is contained in:
1 parent
559a00e66e
commit
320fb6a10b
2 files changed
+78
-7
No files matched your search
@@ -69,6 +69,7 @@ import com.v2ray.ang.ui.base.HelperBaseComponentActivity
|
||||
import com.v2ray.ang.ui.compose.AppTopBar
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import com.v2ray.ang.util.QRCodeDecoder
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import android.util.Size as TargetSize
|
||||
@@ -410,15 +411,14 @@ private fun processImageProxy(
|
||||
return
|
||||
}
|
||||
try {
|
||||
val buffer = imageProxy.planes[0].buffer
|
||||
val bytes = ByteArray(buffer.remaining())
|
||||
buffer.get(bytes)
|
||||
val width = imageProxy.width
|
||||
val height = imageProxy.height
|
||||
val source = PlanarYUVLuminanceSource(
|
||||
bytes, width, height,
|
||||
0, 0, width, height,
|
||||
false
|
||||
val yPlane = imageProxy.planes[0]
|
||||
val source = createYPlaneLuminanceSource(
|
||||
buffer = yPlane.buffer,
|
||||
width = width,
|
||||
height = height,
|
||||
rowStride = yPlane.rowStride,
|
||||
)
|
||||
val binaryBitmap = BinaryBitmap(HybridBinarizer(source))
|
||||
val hints = mapOf(
|
||||
@@ -437,3 +437,26 @@ private fun processImageProxy(
|
||||
imageProxy.close()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun createYPlaneLuminanceSource(
|
||||
buffer: ByteBuffer,
|
||||
width: Int,
|
||||
height: Int,
|
||||
rowStride: Int,
|
||||
): PlanarYUVLuminanceSource {
|
||||
val bufferCopy = buffer.duplicate()
|
||||
val bytes = ByteArray(bufferCopy.remaining())
|
||||
bufferCopy.get(bytes)
|
||||
// Camera HALs may pad each Y row. ZXing advances rows by dataWidth, so the
|
||||
// reported row stride must be used instead of the visible image width.
|
||||
return PlanarYUVLuminanceSource(
|
||||
bytes,
|
||||
rowStride,
|
||||
height,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
false,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.BinaryBitmap
|
||||
import com.google.zxing.DecodeHintType
|
||||
import com.google.zxing.MultiFormatReader
|
||||
import com.google.zxing.common.HybridBinarizer
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class ScannerActivityTest {
|
||||
|
||||
@Test
|
||||
fun paddedCameraRowsDecodeQrCode() {
|
||||
val payload = "vless://00000000-0000-4000-8000-000000006007@example.com:443" +
|
||||
"?encryption=none&security=tls&type=tcp#issue-6007-gallery"
|
||||
val width = 320
|
||||
val height = 320
|
||||
val rowStride = width + 32
|
||||
val qrCode = QRCodeWriter().encode(payload, BarcodeFormat.QR_CODE, width, height)
|
||||
val paddedYPlane = ByteArray(rowStride * height)
|
||||
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
paddedYPlane[y * rowStride + x] = if (qrCode[x, y]) 0 else 0xFF.toByte()
|
||||
}
|
||||
for (x in width until rowStride) {
|
||||
paddedYPlane[y * rowStride + x] = (x + y).toByte()
|
||||
}
|
||||
}
|
||||
|
||||
val source = createYPlaneLuminanceSource(
|
||||
buffer = ByteBuffer.wrap(paddedYPlane),
|
||||
width = width,
|
||||
height = height,
|
||||
rowStride = rowStride,
|
||||
)
|
||||
val hints = mapOf(
|
||||
DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE),
|
||||
DecodeHintType.TRY_HARDER to true,
|
||||
)
|
||||
val result = MultiFormatReader().decode(BinaryBitmap(HybridBinarizer(source)), hints)
|
||||
|
||||
assertEquals(payload, result.text)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user