package com.peachpasswords.codex import android.graphics.Bitmap import com.google.zxing.BinaryBitmap import com.google.zxing.DecodeHintType import com.google.zxing.LuminanceSource import com.google.zxing.RGBLuminanceSource import com.google.zxing.aztec.AztecReader import com.google.zxing.aztec.encoder.Encoder import com.google.zxing.common.BitMatrix import com.google.zxing.common.GlobalHistogramBinarizer import com.google.zxing.common.HybridBinarizer /** Aztec encode/decode for one Codex chunk, via ZXing. */ object CodexAztec { /** * The native, unscaled per-module bit matrix (no ZXing quiet-zone padding * or upscaling) — for drawing the code as PDF vector rectangles rather * than an embedded raster image. See [CodexPdf] for why: PdfDocument's * raster image embedding was found to corrupt crisp module boundaries * (confirmed via a direct in-memory encode-then-decode round trip, which * succeeds 100% of the time, vs. decoding the same code back out of a * generated-then-rendered PDF, which failed almost entirely). Same ECC * percent and (absent) charset/layers hints ZXing's AztecWriter would use * by default — this is byte-for-byte the same code, just the raw module * grid instead of a padded/scaled bitmap. */ fun encodeMatrix(content: String): BitMatrix = Encoder.encode(content, AZTEC_ECC_PERCENT, 0, null).matrix /** * Decode an Aztec code from a tightly-cropped [Bitmap]. A rendered, * cropped single code decodes most reliably with PURE_BARCODE (skip the * locate-in-scene search); fall back to the full detector for codes that * carry surrounding bleed. Null if neither reads. */ fun decode(bitmap: Bitmap): String? { val w = bitmap.width val h = bitmap.height val pixels = IntArray(w * h) bitmap.getPixels(pixels, 0, w, 0, 0, w, h) val source: LuminanceSource = RGBLuminanceSource(w, h, pixels) val pure = mapOf(DecodeHintType.PURE_BARCODE to true, DecodeHintType.TRY_HARDER to true) val scene = mapOf(DecodeHintType.TRY_HARDER to true) // Cross both binarizers (Hybrid is adaptive; Global handles flat, // evenly-lit renders) with both detector modes — marginal codes that // miss one combination usually read on another. val attempts = listOf( BinaryBitmap(HybridBinarizer(source)) to pure, BinaryBitmap(HybridBinarizer(source)) to scene, BinaryBitmap(GlobalHistogramBinarizer(source)) to pure, ) for ((binarizer, hints) in attempts) { try { return AztecReader().decode(binarizer, hints).text } catch (_: Exception) { // try next strategy } } return null } }