Program Resource

開発者向け各種コード、アルゴリズム、リソース情報ライブラリ もしくはねふぁの覚え書き

連結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: 英語

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です


*