CodexAztec.kt

2.8 KB · raw

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}