Program Resource

Resource libraries for programmers and developers

Writing out structured array QR code, in my case, pages of QR code was fixed to 4 and data size per code was fixed, so I didn’ t create function to automatically split data to multiple QR codes. It’s best if library handles one big data to be encoded, and automatically split into appropriate QR codes, and return multiple QR code bitmaps, but this will need big modification in source code.

I only made modification to Zxing source code as follow:

  • Add function name to create structured array QR code
  • Add flag, current page, total page, and parity to header data

Split data, calculating pages, and parity is done on app side, and Zxing simply creates single QR code with header containing structured array QR code.

I made 2 modifications in Zxing source. Source base is 2.0, so it may differ in latest version.

First, in QRCodeWriter.java 47th line to 78th line, added “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,

From app, page number, parity, and data for specified page is handled upon calling function above.

Next, “encode_sa” is added in Encoder.java from line 82 to 150.

 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,

Inside encode_sa is mostly copy of encode function, and header data part is added in Step 4.1. After making modificaiton, use buildcore.bat from Step 1 and rebuild core.java, then copy to android project file.

And last, caller part in app.

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 contains all data, and page number is specified when calling this function. Since QR code in Animal Crossing was fixed to 0x21c size per QR code, 0x21c size data is taken out from whole, then QR coded.

Parity is same for all structured array QR in group, parity is calculated against whole data. I coded parity calculation function a while ago, so I’m not sure this calculation is correct or not. However, it seems this parity is only checked for matching codes, and not used to calculate parity; it might be fine to just fill random number from 0 – 255 (though there may be some reader which also calculates parity).

Well, that’s all for what I’ve done to make structured array QR code reading support in Animal Crossing design app.

Hope this info may help someone who needs.


This post is also available in: Japanese

Leave a Reply

Your email address will not be published. Required fields are marked *


*