CodexAztec.kt
1package com.peachpasswords.codex23import android.graphics.Bitmap4import com.google.zxing.BinaryBitmap5import com.google.zxing.DecodeHintType6import com.google.zxing.LuminanceSource7import com.google.zxing.RGBLuminanceSource8import com.google.zxing.aztec.AztecReader9import com.google.zxing.aztec.encoder.Encoder10import com.google.zxing.common.BitMatrix11import com.google.zxing.common.GlobalHistogramBinarizer12import com.google.zxing.common.HybridBinarizer1314/** Aztec encode/decode for one Codex chunk, via ZXing. */15object CodexAztec {1617 /**18 * The native, unscaled per-module bit matrix (no ZXing quiet-zone padding19 * or upscaling) — for drawing the code as PDF vector rectangles rather20 * than an embedded raster image. See [CodexPdf] for why: PdfDocument's21 * raster image embedding was found to corrupt crisp module boundaries22 * (confirmed via a direct in-memory encode-then-decode round trip, which23 * succeeds 100% of the time, vs. decoding the same code back out of a24 * generated-then-rendered PDF, which failed almost entirely). Same ECC25 * percent and (absent) charset/layers hints ZXing's AztecWriter would use26 * by default — this is byte-for-byte the same code, just the raw module27 * grid instead of a padded/scaled bitmap.28 */29 fun encodeMatrix(content: String): BitMatrix =30 Encoder.encode(content, AZTEC_ECC_PERCENT, 0, null).matrix3132 /**33 * Decode an Aztec code from a tightly-cropped [Bitmap]. A rendered,34 * cropped single code decodes most reliably with PURE_BARCODE (skip the35 * locate-in-scene search); fall back to the full detector for codes that36 * carry surrounding bleed. Null if neither reads.37 */38 fun decode(bitmap: Bitmap): String? {39 val w = bitmap.width40 val h = bitmap.height41 val pixels = IntArray(w * h)42 bitmap.getPixels(pixels, 0, w, 0, 0, w, h)43 val source: LuminanceSource = RGBLuminanceSource(w, h, pixels)44 val pure = mapOf(DecodeHintType.PURE_BARCODE to true, DecodeHintType.TRY_HARDER to true)45 val scene = mapOf(DecodeHintType.TRY_HARDER to true)46 // Cross both binarizers (Hybrid is adaptive; Global handles flat,47 // evenly-lit renders) with both detector modes — marginal codes that48 // miss one combination usually read on another.49 val attempts = listOf(50 BinaryBitmap(HybridBinarizer(source)) to pure,51 BinaryBitmap(HybridBinarizer(source)) to scene,52 BinaryBitmap(GlobalHistogramBinarizer(source)) to pure,53 )54 for ((binarizer, hints) in attempts) {55 try {56 return AztecReader().decode(binarizer, hints).text57 } catch (_: Exception) {58 // try next strategy59 }60 }61 return null62 }63}