QRコード認識/生成ライブラリZXing3.2.2でマスクパターンを取得し完全に同じQRコードを再現
とあるアプリを試しに作ってたら必要になったのでメモ。もしかしたら元のZXingでうまくやればできるかもしれない。
なんか書いてみたらだからどうしたってレベルだったけど一応
概要
- QRコードではモジュールの偏りを抑えるために最後に8種類のマスクパターンのうち1つをかける
変更点
core/src/main/java/com/google/zxing/common/DecoderResult.java
public final class DecoderResult { //...// private Integer erasures; private Object other; private final int structuredAppendParity; private final int structuredAppendSequenceNumber; //sabikoi added private int maskIndex; public int getMaskIndex() { return maskIndex; } public void setMaskIndex(int index) { maskIndex = index; } //...// this.text = text; this.byteSegments = byteSegments; this.ecLevel = ecLevel; this.structuredAppendParity = saParity; this.structuredAppendSequenceNumber = saSequence; //sabikoi added maskIndex = -1; } public byte[] getRawBytes() { return rawBytes; }
マスクデータを保存する変数をリザルトのクラスに追加しただけ
core/src/main/java/com/google/zxing/Result.java
public final class Result { //...// private final byte[] rawBytes; private ResultPoint[] resultPoints; private final BarcodeFormat format; private Map<ResultMetadataType,Object> resultMetadata; private final long timestamp; //sabikoi added private int maskIndex; public int getMaskIndex() { return maskIndex; } public void setMaskIndex(int index) { maskIndex = index; } //...// this.rawBytes = rawBytes; this.resultPoints = resultPoints; this.format = format; this.resultMetadata = null; this.timestamp = timestamp; //sabikoi added maskIndex = -1; }
こちらも同様
core/src/main/java/com/google/zxing/qrcode/QRCodeReader.java
//...// @Override public final Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints) throws NotFoundException, ChecksumException, FormatException { //...// Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE); //sabikoi added result.setMaskIndex(decoderResult.getMaskIndex()); List<byte[]> byteSegments = decoderResult.getByteSegments(); if (byteSegments != null) { result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments); }
そして外部から利用できるようにQRCoderReaderが返すResultオブジェクトにセット(内部的には一旦デコーダから帰ってきたDecoderResultを受け取ってResultを作っている)
core/src/main/java/com/google/zxing/qrcode/decoder/Decoder.java
//...// public DecoderResult decode(BitMatrix bits, Map<DecodeHintType,?> hints) throws FormatException, ChecksumException { //...// // Prepare for a mirrored reading. parser.mirror(); DecoderResult result = decode(parser, hints); //sabikoi added result.setMaskIndex(parser.getMaskIndex()); // Success! Notify the caller that the code was mirrored. result.setOther(new QRCodeDecoderMetaData(true)); return result; //...// private DecoderResult decode(BitMatrixParser parser, Map<DecodeHintType,?> hints) throws FormatException, ChecksumException { //...// // Error-correct and copy data blocks together into a stream of bytes for (DataBlock dataBlock : dataBlocks) { byte[] codewordBytes = dataBlock.getCodewords(); int numDataCodewords = dataBlock.getNumDataCodewords(); correctErrors(codewordBytes, numDataCodewords); for (int i = 0; i < numDataCodewords; i++) { resultBytes[resultOffset++] = codewordBytes[i]; } } //sabikoi modified DecoderResult res = DecodedBitStreamParser.decode(resultBytes, version, ecLevel, hints); res.setMaskIndex(parser.getMaskIndex()); return res; // Decode the contents of that stream of bytes // return DecodedBitStreamParser.decode(resultBytes, version, ecLevel, hints);
このままでは間違いなく-1が返ってくるので実際にマスクパターンを処理してるDecoderにリザルトへのセットを追加
core/src/main/java/com/google/zxing/qrcode/decoder/BitMatrixParser.java
final class BitMatrixParser { //...// /** * Revert the mask removal done while reading the code words. The bit matrix should revert to its original state. */ void remask() { if (parsedFormatInfo == null) { return; // We have no format information, and have no data mask } DataMask dataMask = DataMask.forReference(parsedFormatInfo.getDataMask()); int dimension = bitMatrix.getHeight(); dataMask.unmaskBitMatrix(bitMatrix, dimension); } //sabikoi added public int getMaskIndex(){ if(parsedFormatInfo == null){ return -1; } return parsedFormatInfo.getDataMask(); } /** * Prepare the parser for a mirrored operation. * This flag has effect only on the {@link #readFormatInformation()} and the * {@link #readVersion()}. Before proceeding with {@link #readCodewords()} the * {@link #mirror()} method should be called. * * @param mirror Whether to read version and format information mirrored. */ void setMirror(boolean mirror) { parsedVersion = null; parsedFormatInfo = null; this.mirror = mirror; }
マスクパターンのインデックスは内部的にはparserに保持されてるので取得できるようにする
これでQRを読み込んだ後にリザルトから以下のように取得可能
QRCodeReader reader = new QRCodeReader(); Result result = null; result = reader.decode(bitmap); int qrMaskIndex = result.getMaskIndex();
core/src/main/java/com/google/zxing/EncodeHintType.java
/** * Specifies the required number of layers for an Aztec code. * A negative number (-1, -2, -3, -4) specifies a compact Aztec code. * 0 indicates to use the minimum number of layers (the default). * A positive number (1, 2, .. 32) specifies a normal (non-compact) Aztec code. * (Type {@link Integer}, or {@link String} representation of the integer value). */ AZTEC_LAYERS, /** * sabikoi added */ MASK_INDEX }
core/src/main/java/com/google/zxing/qrcode/encoder/Encoder.java
public static QRCode encode(String content, ErrorCorrectionLevel ecLevel, Map<EncodeHintType,?> hints) throws WriterException { //...// // Choose the mask pattern and set to "qrCode". int dimension = version.getDimensionForVersion(); ByteMatrix matrix = new ByteMatrix(dimension, dimension); //sabikoi modified int maskPattern = ( (hints != null && hints.containsKey(EncodeHintType.MASK_INDEX)) ? (Integer)hints.get(EncodeHintType.MASK_INDEX) : chooseMaskPattern(finalBits, ecLevel, version, matrix)); qrCode.setMaskPattern(maskPattern); if (hints != null && hints.containsKey(EncodeHintType.CHARACTER_SET)) { encoding = hints.get(EncodeHintType.CHARACTER_SET).toString(); }
生成する時はEncodeHintとして設定できるようにした
使う時はさっき取得したインデックスを保存しておいて指定するだけ
hints.put(EncodeHintType.ERROR_CORRECTION, qrErrorLevel); hints.put(EncodeHintType.MASK_INDEX, qrMaskData); QRCodeWriter writer = new QRCodeWriter(); BitMatrix bitMatrix = writer.encode(new String(qrContent, "ISO_8859_1"), BarcodeFormat.QR_CODE, width, height, hints);
ソースからのAndroidプロジェクトへの組み込み
mavenをインストールしてzxingのルートフォルダでビルド、core-3.2.2-SNAPSHOT.jarとjavase-3.2.2-SNAPSHOT.jarをライブラリとして追加するだけ(多分)
結果
プリパラのQRをカメラで認識してデータとして取得・全く同じものの画像を作成までできたぞ… pic.twitter.com/WobFEKE4Zi
— さびたコイル (@rusted_coil) 2015, 11月 27