package com.peachpasswords.codex import androidx.camera.core.ImageProxy import com.google.zxing.BinaryBitmap import com.google.zxing.DecodeHintType import com.google.zxing.PlanarYUVLuminanceSource import com.google.zxing.aztec.AztecReader import com.google.zxing.common.GlobalHistogramBinarizer import com.google.zxing.common.HybridBinarizer /** * Live-camera Aztec decode for the Codex restore camera path (D10). * * Distinct from [CodexAztec.decode], which decodes a tightly-cropped render of a * single PDF cell. A camera frame is a full scene that may contain one Aztec among * the printed 6×6 grid, so we use the scene detector (no PURE_BARCODE) and read the * Y plane directly — converting the YUV_420_888 frame to an ARGB bitmap every frame * would be wasteful at preview frame rates. */ object CodexAztecFrame { /** * Decode the first readable Aztec code from a CameraX [ImageProxy] * (YUV_420_888). Returns the raw text, or null if no code reads. Does NOT * close the proxy — the caller owns its lifecycle. */ fun decode(image: ImageProxy): String? { val plane = image.planes.getOrNull(0) ?: return null val buffer = plane.buffer val data = ByteArray(buffer.remaining()) buffer.get(data) val width = image.width val height = image.height val rowStride = plane.rowStride // PlanarYUVLuminanceSource expects a tightly-packed Y plane; when the // camera reports row padding (rowStride > width) we repack row by row. val luma: ByteArray = if (rowStride == width) { data } else { val packed = ByteArray(width * height) for (row in 0 until height) { System.arraycopy(data, row * rowStride, packed, row * width, width) } packed } val source = PlanarYUVLuminanceSource( luma, width, height, 0, 0, width, height, false, ) val hints = mapOf(DecodeHintType.TRY_HARDER to true) // Cross both binarizers: Hybrid adapts to uneven lighting, Global handles // flat well-lit prints. Rotating the source covers codes shot sideways. val attempts = listOf( BinaryBitmap(HybridBinarizer(source)), BinaryBitmap(GlobalHistogramBinarizer(source)), BinaryBitmap(HybridBinarizer(source.rotateCounterClockwise())), ) for (binary in attempts) { try { return AztecReader().decode(binary, hints).text } catch (_: Exception) { // try next strategy } } return null } }