連結QRコードの作成であるが、筆者が連結QRが必要となったとび森のデータでは連結QRは4枚で各コードのサイズは固定であったため、データを元に分割数を決めると言った処理は省略している。本来はライブラリ側で受け取ったデータを適切に分割し、複数枚のQR Bitmapデータを返すべきであるがそこまでの修正は大変なので、筆者がZxingライブラリに行った修正は以下の点だけである。
- 連結QR作成用関数の追加
- ヘッダー部分に連結フラグ、ページ、パリティを追加する
データの分割、ページ数及びパリティはアプリ側であらかじめ準備しておき、Zxingでは単一QRの作成と処理はほぼ同様で、ヘッダー部分だけ連結の内容を組み込めるようにしただけである。
Zxingのソースで修正したのは2箇所。2.0のソースベースなので、最新版では異なる可能性がある。
まずはQRCodeWriter.javaの47行目から78行目に「encode_sa」を追加。
public final class QRCodeWriter implements Writer {
private static final int QUIET_ZONE_SIZE = 4;
@Override
public BitMatrix encode(String contents, BarcodeFormat format, int width, int height)
throws WriterException {
return encode(contents, format, width, height, null);
}
public BitMatrix encode_sa(byte page, byte totalpage, int parity,
String contents,
BarcodeFormat format,
int width,
int height,
Map<EncodeHintType,?> hints) throws WriterException {
if (contents.length() == 0) {
throw new IllegalArgumentException("Found empty contents");
}
if (format != BarcodeFormat.QR_CODE) {
throw new IllegalArgumentException("Can only encode QR_CODE, but got " + format);
}
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Requested dimensions are too small: " + width + 'x' +
height);
}
ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;
if (hints != null) {
ErrorCorrectionLevel requestedECLevel = (ErrorCorrectionLevel) hints.get(EncodeHintType.ERROR_CORRECTION);
if (requestedECLevel != null) {
errorCorrectionLevel = requestedECLevel;
}
}
QRCode code = new QRCode();
Encoder.encode_sa(page,totalpage,parity,contents, errorCorrectionLevel, hints, code);
return renderResult(code, width, height);
}
@Override
public BitMatrix encode(String contents,
アプリからは上記関数を呼び出す際、ページ番号やパリティ、そのページ用のQRコードデータを渡す。次にEncoder.javaの82行目から150行目に「encode_sa」を追加。
public static void encode(String content, ErrorCorrectionLevel ecLevel, QRCode qrCode)
throws WriterException {
encode(content, ecLevel, null, qrCode);
}
//structured arrayed data, NeFa 2013/3/4
//data page split and parity calcuated on caller side
public static void encode_sa(byte page, byte totalpage, int parity, String content,
ErrorCorrectionLevel ecLevel,
Map<EncodeHintType,?> hints,
QRCode qrCode) throws WriterException {
String encoding = hints == null ? null : (String) hints.get(EncodeHintType.CHARACTER_SET);
if (encoding == null) {
encoding = DEFAULT_BYTE_MODE_ENCODING;
}
// Step 1: Choose the mode (encoding).
Mode mode = chooseMode(content, encoding);
// Step 2: Append "bytes" into "dataBits" in appropriate encoding.
BitArray dataBits = new BitArray();
appendBytes(content, mode, dataBits, encoding);
// Step 3: Initialize QR code that can contain "dataBits".
int numInputBits = dataBits.getSize();
initQRCode(numInputBits, ecLevel, mode, qrCode);
// Step 4: Build another bit vector that contains header and data.
BitArray headerAndDataBits = new BitArray();
// Step 4.1: Add code page info
byte tmpbyte;
tmpbyte = 0x3;
headerAndDataBits.appendBits(tmpbyte, 4);
headerAndDataBits.appendBits(page, 4);
headerAndDataBits.appendBits(totalpage, 4);
headerAndDataBits.appendBits((byte)((parity&0xf0)>>4), 4);
headerAndDataBits.appendBits((byte)(parity&0x0f), 4);
// Step 4.5: Append ECI message if applicable
if (mode == Mode.BYTE && !DEFAULT_BYTE_MODE_ENCODING.equals(encoding)) {
CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding);
if (eci != null) {
appendECI(eci, headerAndDataBits);
}
}
appendModeInfo(mode, headerAndDataBits);
int numLetters = mode == Mode.BYTE ? dataBits.getSizeInBytes() : content.length();
appendLengthInfo(numLetters, qrCode.getVersion(), mode, headerAndDataBits);
headerAndDataBits.appendBitArray(dataBits);
// Step 5: Terminate the bits properly.
terminateBits(qrCode.getNumDataBytes(), headerAndDataBits);
// Step 6: Interleave data bits with error correction code.
BitArray finalBits = new BitArray();
interleaveWithECBytes(headerAndDataBits, qrCode.getNumTotalBytes(), qrCode.getNumDataBytes(),
qrCode.getNumRSBlocks(), finalBits);
// Step 7: Choose the mask pattern and set to "qrCode".
ByteMatrix matrix = new ByteMatrix(qrCode.getMatrixWidth(), qrCode.getMatrixWidth());
qrCode.setMaskPattern(chooseMaskPattern(finalBits, ecLevel, qrCode.getVersion(), matrix));
// Step 8. Build the matrix and set it to "qrCode".
MatrixUtil.buildMatrix(finalBits, ecLevel, qrCode.getVersion(), qrCode.getMaskPattern(), matrix);
qrCode.setMatrix(matrix);
// Step 9. Make sure we have a valid QR Code.
if (!qrCode.isValid()) {
throw new WriterException("Invalid QR code: " + qrCode.toString());
}
}
public static void encode(String content,
中身はencode関数のほぼコピーで、Step4.1のヘッダー情報を追加しているくらいである。修正後、Step1で準備しておいたbuildcore.batを実行してcore.jarをリビルド、androidのプロジェクトに組み込み直す。
そして、アプリの呼び出し部。
private byte calcParity(byte[] data){
byte parity;
parity = data[0];
for (int i=1;i<data.length;i++)
parity = (byte) (parity ^ data[i]);//XOR
return parity;
}
private static final String ENCORD_NAME = "ISO-8859-1";
public Bitmap createBinQRCode(byte[] qrData,int size,int page) {
String contents;
Bitmap bitmap = null;
try {
QRCodeWriter writer = new QRCodeWriter();
Hashtable encodeHint = new Hashtable();
encodeHint.put(EncodeHintType.CHARACTER_SET, ENCORD_NAME);
encodeHint.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
BitMatrix bitData;
byte partbuff[] = new byte[0x21c];
System.arraycopy(qrData,0x21c*page,partbuff,0,0x21c);
contents = new String(partbuff, ENCORD_NAME);
bitData = writer.encode_sa((byte)(page&0xff),(byte)3,calcParity(qrData),contents, BarcodeFormat.QR_CODE, size, size, encodeHint);
qrDataに全データが入っており、本関数を呼び出す度にpageを指定しQRコードを生成している。とび森のQRコードは1コードあたり0x21cサイズ固定だったため、指定ページにあたるデータの0x21c分のみ抜き出してQRコード化している。
Parityは全連結QRコード共通の為、全データに対するParityを算出。この算出コードを組んだのがかなり前なのでこれで正しいか覚えていないが、おそらくほとんどの連結QRコード読み取りは連結用IDとして扱いParity計算まではしていない気もするので、適当な乱数をParityとして設定しても問題なさそうな気はする。
とりあえず以上で簡単ではあるが筆者がとび森用に連結QRコード読み書き対応を行った内容である。
誰かの参考にあれば幸いである。
参考にしたサイト:
http://wada811.blog.fc2.com/blog-entry-56.html
http://d.hatena.ne.jp/tomorrowkey/20091114/1258206013
http://ahoworld.blog58.fc2.com/?mode=m&no=255
http://wiashia.blog.so-net.ne.jp/2012-05-23
This post is also available in: 英語




