uk.org.okapibarcode.backend.MicroQrCode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of okapibarcode Show documentation
Show all versions of okapibarcode Show documentation
An open-source barcode generator written entirely in Java, supporting over 50 encoding standards including all ISO standards.
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.org.okapibarcode.backend;
import static uk.org.okapibarcode.util.Arrays.positionOf;
import java.io.UnsupportedEncodingException;
/**
* Implements Micro QR Code
* According to ISO/IEC 18004:2006
*
* A miniature version of the QR Code symbol for short messages.
* QR Code symbols can encode characters in the Latin-1 set and Kanji
* characters which are members of the Shift-JIS encoding scheme.
*
* @author Robin Stuart
*/
public class MicroQrCode extends Symbol {
private enum qrMode {
NULL, KANJI, BINARY, ALPHANUM, NUMERIC
}
public enum EccMode {
L, M, Q, H
}
private qrMode[] inputMode;
private String binary;
private int[] binaryCount = new int[4];
private int[] grid;
private int[] eval;
private int preferredVersion;
/**
* Sets the preferred symbol size. This value may be ignored if the
* data string is too large to fit into the specified symbol. Input
* values correspond to symbol sizes as shown in the following table.
*
*
*
* Input
* Version
* Symbol Size
*
*
* 1
* M1
* 11 x 11
*
*
* 2
* M2
* 13 x 13
*
*
* 3
* M3
* 15 x 15
*
*
* 4
* M4
* 17 x 17
*
*
*
*
* @param version Symbol size
*/
public void setPreferredVersion(int version) {
preferredVersion = version;
}
private EccMode preferredEccLevel = EccMode.L;
/**
* Set the amount of symbol space allocated to error correction.
* Levels are predefined according to the following table:
*
*
*
* ECC Level
* Error Correction Capacity
* Recovery Capacity
*
*
* L (default)
* Approx 20% of symbol
* Approx 7%
*
*
* M
* Approx 37% of symbol
* Approx 15%
*
*
* Q
* Approx 55% of symbol
* Approx 25%
*
*
* H
* Approx 65% of symbol
* Approx 30%
*
*
*
* @param eccMode Error correction level
*/
public void setEccMode (EccMode eccMode) {
preferredEccLevel = eccMode;
}
/* Table 5 - Encoding/Decoding table for Alphanumeric mode */
private static final char[] RHODIUM = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*', '+', '-',
'.', '/', ':'
};
private static final int[] QR_ANNEX_C1 = {
/* Micro QR Code format information */
0x4445, 0x4172, 0x4e2b, 0x4b1c, 0x55ae, 0x5099, 0x5fc0, 0x5af7, 0x6793,
0x62a4, 0x6dfd, 0x68ca, 0x7678, 0x734f, 0x7c16, 0x7921, 0x06de, 0x03e9,
0x0cb0, 0x0987, 0x1735, 0x1202, 0x1d5b, 0x186c, 0x2508, 0x203f, 0x2f66,
0x2a51, 0x34e3, 0x31d4, 0x3e8d, 0x3bba
};
private static final int[] MICRO_QR_SIZES = {
11, 13, 15, 17
};
@Override
protected void encode() {
int i, j, size;
boolean[] version_valid = new boolean[4];
int n_count, a_count;
EccMode ecc_level;
int version, autoversion;
int bitmask;
int format, format_full;
StringBuilder bin = new StringBuilder();
boolean byteModeUsed;
boolean alphanumModeUsed;
boolean kanjiModeUsed;
if (content.length() > 35) {
throw new OkapiException("Input data too long");
}
inputCharCheck();
for (i = 0; i < 4; i++) {
version_valid[i] = true;
}
inputMode = new qrMode[40];
selectEncodingMode();
n_count = 0;
a_count = 0;
for (i = 0; i < content.length(); i++) {
if ((content.charAt(i) >= '0') && (content.charAt(i) <= '9')) {
n_count++;
}
if (isAlphanumeric(content.charAt(i))) {
a_count++;
}
}
if (a_count == content.length()) {
/* All data can be encoded in Alphanumeric mode */
for (i = 0; i < content.length(); i++) {
inputMode[i] = qrMode.ALPHANUM;
}
}
if (n_count == content.length()) {
/* All data can be encoded in Numeric mode */
for (i = 0; i < content.length(); i++) {
inputMode[i] = qrMode.NUMERIC;
}
}
byteModeUsed = false;
alphanumModeUsed = false;
kanjiModeUsed = false;
for (i = 0; i < content.length(); i++) {
if (inputMode[i] == qrMode.BINARY) {
byteModeUsed = true;
}
if (inputMode[i] == qrMode.ALPHANUM) {
alphanumModeUsed = true;
}
if (inputMode[i] == qrMode.KANJI) {
kanjiModeUsed = true;
}
}
getBinaryLength();
/* Eliminate possivle versions depending on type of content */
if (byteModeUsed) {
version_valid[0] = false;
version_valid[1] = false;
}
if (alphanumModeUsed) {
version_valid[0] = false;
}
if (kanjiModeUsed) {
version_valid[0] = false;
version_valid[1] = false;
}
/* Eliminate possible versions depending on length of binary data */
if (binaryCount[0] > 20) {
version_valid[0] = false;
}
if (binaryCount[1] > 40) {
version_valid[1] = false;
}
if (binaryCount[2] > 84) {
version_valid[2] = false;
}
if (binaryCount[3] > 128) {
throw new OkapiException("Input data too long");
}
/* Eliminate possible versions depending on error correction level specified */
ecc_level = preferredEccLevel;
if (ecc_level == EccMode.H) {
throw new OkapiException("Error correction level H not available");
}
if (ecc_level == EccMode.Q) {
version_valid[0] = false;
version_valid[1] = false;
version_valid[2] = false;
if (binaryCount[3] > 80) {
throw new OkapiException("Input data too long");
}
}
if (ecc_level == EccMode.M) {
version_valid[0] = false;
if (binaryCount[1] > 32) {
version_valid[1] = false;
}
if (binaryCount[2] > 68) {
version_valid[2] = false;
}
if (binaryCount[3] > 112) {
throw new OkapiException("Input data too long");
}
}
autoversion = 3;
if (version_valid[2]) {
autoversion = 2;
}
if (version_valid[1]) {
autoversion = 1;
}
if (version_valid[0]) {
autoversion = 0;
}
version = autoversion;
/* Get version from user */
if((preferredVersion >= 1) && (preferredVersion <= 4)) {
if(preferredVersion >= autoversion) {
version = preferredVersion;
}
}
/* If there is enough unused space then increase the error correction level */
if (version == 3) {
if (binaryCount[3] <= 112) {
ecc_level = EccMode.M;
}
if (binaryCount[3] <= 80) {
ecc_level = EccMode.Q;
}
}
if (version == 2 && binaryCount[2] <= 68) {
ecc_level = EccMode.M;
}
if (version == 1 && binaryCount[1] <= 32) {
ecc_level = EccMode.M;
}
binary = "";
generateBinary(version);
if (binary.length() > 128) {
throw new OkapiException("Input data too long");
}
switch (version) {
case 0:
generateM1Symbol();
infoLine("Version: M1");
break;
case 1:
generateM2Symbol(ecc_level);
infoLine("Version: M2");
infoLine("ECC Level: " + levelToLetter(ecc_level));
break;
case 2:
generateM3Symbol(ecc_level);
infoLine("Version: M3");
infoLine("ECC Level: " + levelToLetter(ecc_level));
break;
case 3:
generateM4Symbol(ecc_level);
infoLine("Version: M4");
infoLine("ECC Level: " + levelToLetter(ecc_level));
break;
}
size = MICRO_QR_SIZES[version];
grid = new int[size * size];
for (i = 0; i < size; i++) {
for (j = 0; j < size; j++) {
grid[(i * size) + j] = 0;
}
}
setupBitGrid(size);
populateBitGrid(size);
bitmask = applyBitmask(size);
infoLine("Mask Pattern: " + Integer.toBinaryString(bitmask));
/* Add format data */
format = 0;
switch (version) {
case 1:
switch (ecc_level) {
case L:
format = 1;
break;
case M:
format = 2;
break;
}
break;
case 2:
switch (ecc_level) {
case L:
format = 3;
break;
case M:
format = 4;
break;
}
break;
case 3:
switch (ecc_level) {
case L:
format = 5;
break;
case M:
format = 6;
break;
case Q:
format = 7;
break;
}
break;
}
format_full = QR_ANNEX_C1[(format << 2) + bitmask];
if ((format_full & 0x4000) != 0) {
grid[(8 * size) + 1] += 0x01;
}
if ((format_full & 0x2000) != 0) {
grid[(8 * size) + 2] += 0x01;
}
if ((format_full & 0x1000) != 0) {
grid[(8 * size) + 3] += 0x01;
}
if ((format_full & 0x800) != 0) {
grid[(8 * size) + 4] += 0x01;
}
if ((format_full & 0x400) != 0) {
grid[(8 * size) + 5] += 0x01;
}
if ((format_full & 0x200) != 0) {
grid[(8 * size) + 6] += 0x01;
}
if ((format_full & 0x100) != 0) {
grid[(8 * size) + 7] += 0x01;
}
if ((format_full & 0x80) != 0) {
grid[(8 * size) + 8] += 0x01;
}
if ((format_full & 0x40) != 0) {
grid[(7 * size) + 8] += 0x01;
}
if ((format_full & 0x20) != 0) {
grid[(6 * size) + 8] += 0x01;
}
if ((format_full & 0x10) != 0) {
grid[(5 * size) + 8] += 0x01;
}
if ((format_full & 0x08) != 0) {
grid[(4 * size) + 8] += 0x01;
}
if ((format_full & 0x04) != 0) {
grid[(3 * size) + 8] += 0x01;
}
if ((format_full & 0x02) != 0) {
grid[(2 * size) + 8] += 0x01;
}
if ((format_full & 0x01) != 0) {
grid[(1 * size) + 8] += 0x01;
}
readable = "";
pattern = new String[size];
row_count = size;
row_height = new int[size];
for (i = 0; i < size; i++) {
bin.setLength(0);
for (j = 0; j < size; j++) {
if ((grid[(i * size) + j] & 0x01) != 0) {
bin.append('1');
} else {
bin.append('0');
}
}
pattern[i] = bin2pat(bin);
row_height[i] = 1;
}
}
private void inputCharCheck() {
int qmarkBefore, qmarkAfter;
int i;
byte[] temp;
/* Check that input includes valid characters */
if (content.matches("[\u0000-\u00FF]+")) {
/* All characters in ISO 8859-1 */
return;
}
/* Otherwise check for Shift-JIS characters */
qmarkBefore = 0;
for (i = 0; i < content.length(); i++) {
if (content.charAt(i) == '?') {
qmarkBefore++;
}
}
try {
temp = content.getBytes("SJIS");
} catch (UnsupportedEncodingException e) {
throw new OkapiException("Character encoding error");
}
qmarkAfter = 0;
for (i = 0; i < temp.length; i++) {
if (temp[i] == '?') {
qmarkAfter++;
}
}
/* If these values are the same, conversion was successful */
if (qmarkBefore != qmarkAfter) {
throw new OkapiException("Invalid characters in input data");
}
}
private char levelToLetter(EccMode ecc_mode) {
switch (ecc_mode) {
case L:
return 'L';
case M:
return 'M';
case Q:
return 'Q';
case H:
return 'H';
default:
return ' ';
}
}
private void selectEncodingMode() {
int i, j;
int mlen;
int length = content.length();
for (i = 0; i < length; i++) {
if (content.charAt(i) > 0xff) {
inputMode[i] = qrMode.KANJI;
} else {
inputMode[i] = qrMode.BINARY;
if (isAlphanumeric(content.charAt(i))) {
inputMode[i] = qrMode.ALPHANUM;
}
if ((content.charAt(i) >= '0') && (content.charAt(i) <= '9')) {
inputMode[i] = qrMode.NUMERIC;
}
}
}
/* If less than 6 numeric digits together then don't use numeric mode */
for (i = 0; i < length; i++) {
if (inputMode[i] == qrMode.NUMERIC) {
if (((i != 0) && (inputMode[i - 1] != qrMode.NUMERIC))
|| (i == 0)) {
mlen = 0;
while (((mlen + i) < length)
&& (inputMode[mlen + i] == qrMode.NUMERIC)) {
mlen++;
}
if (mlen < 6) {
for (j = 0; j < mlen; j++) {
inputMode[i + j] = qrMode.ALPHANUM;
}
}
}
}
}
/* If less than 4 alphanumeric characters together then don't use alphanumeric mode */
for (i = 0; i < length; i++) {
if (inputMode[i] == qrMode.ALPHANUM) {
if (((i != 0) && (inputMode[i - 1] != qrMode.ALPHANUM))
|| (i == 0)) {
mlen = 0;
while (((mlen + i) < length)
&& (inputMode[mlen + i] == qrMode.ALPHANUM)) {
mlen++;
}
if (mlen < 6) {
for (j = 0; j < mlen; j++) {
inputMode[i + j] = qrMode.BINARY;
}
}
}
}
}
}
private boolean isAlphanumeric(char cglyph) {
/* Returns true if input glyph is in the Alphanumeric set */
boolean retval = false;
if ((cglyph >= '0') && (cglyph <= '9')) {
retval = true;
}
if ((cglyph >= 'A') && (cglyph <= 'Z')) {
retval = true;
}
switch (cglyph) {
case ' ':
case '$':
case '%':
case '*':
case '+':
case '-':
case '.':
case '/':
case ':':
retval = true;
break;
}
return retval;
}
private String toBinary(int data, int h) {
String argument = "";
for (;
(h != 0); h >>= 1) {
if ((data & h) != 0) {
argument += "1";
} else {
argument += "0";
}
}
return argument;
}
private void getBinaryLength() {
int i;
qrMode currentMode = qrMode.NULL;
int blockLength;
/* Always include a terminator */
for (i = 0; i < 4; i++) {
binaryCount[i] = 0;
}
for (i = 0; i < content.length(); i++) {
if(currentMode != inputMode[i]) {
blockLength = 0;
do {
blockLength++;
} while (((i + blockLength) < content.length())
&& (inputMode[i + blockLength] == inputMode[i]));
switch (inputMode[i]) {
case KANJI:
binaryCount[2] += 5 + (blockLength * 13);
binaryCount[3] += 7 + (blockLength * 13);
break;
case BINARY:
binaryCount[2] += 6 + (blockLength * 8);
binaryCount[3] += 8 + (blockLength * 8);
break;
case ALPHANUM:
int alphaLength;
if ((blockLength % 2) == 1) {
/* Odd length block */
alphaLength = ((blockLength - 1) / 2) * 11;
alphaLength += 6;
} else {
/* Even length block */
alphaLength = (blockLength / 2) * 11;
}
binaryCount[1] += 4 + alphaLength;
binaryCount[2] += 6 + alphaLength;
binaryCount[3] += 8 + alphaLength;
break;
case NUMERIC:
int numLength;
switch(blockLength % 3) {
case 1:
/* one digit left over */
numLength = ((blockLength - 1) / 3) * 10;
numLength += 4;
break;
case 2:
/* two digits left over */
numLength = ((blockLength - 2) / 3) * 10;
numLength += 7;
break;
default:
/* blockLength is a multiple of 3 */
numLength = (blockLength / 3) * 10;
break;
}
binaryCount[0] += 3 + numLength;
binaryCount[1] += 5 + numLength;
binaryCount[2] += 7 + numLength;
binaryCount[3] += 9 + numLength;
break;
}
currentMode = inputMode[i];
}
}
/* Add terminator */
if (binaryCount[1] < 37) {
binaryCount[1] += 5;
}
if (binaryCount[2] < 81) {
binaryCount[2] += 7;
}
if (binaryCount[3] < 125) {
binaryCount[3] += 9;
}
}
private void generateBinary(int version) {
int position = 0;
int blockLength, i;
qrMode data_block;
int msb, lsb, prod, jis;
String oneChar;
byte[] jisBytes;
int count, first, second, third;
info("Encoding: ");
do {
data_block = inputMode[position];
blockLength = 0;
do {
blockLength++;
} while (((blockLength + position) < content.length())
&& (inputMode[position + blockLength] == data_block));
switch (data_block) {
case KANJI:
/* Kanji mode */
/* Mode indicator */
switch (version) {
case 2:
binary += "11";
break;
case 3:
binary += "011";
break;
}
/* Character count indicator */
binary += toBinary(blockLength, 1 << version); /* version = 2..3 */
info("KANJ (" + blockLength + ") ");
/* Character representation */
for (i = 0; i < blockLength; i++) {
oneChar = "";
oneChar += content.charAt(position + i);
/* Convert Unicode input to Shift-JIS */
try {
jisBytes = oneChar.getBytes("SJIS");
} catch (UnsupportedEncodingException e) {
throw new OkapiException("Character encoding error");
}
jis = ((jisBytes[0] & 0xFF) << 8) + (jisBytes[1] & 0xFF);
if (jis > 0x9fff) {
jis -= 0xc140;
} else {
jis -= 0x8140;
}
msb = (jis & 0xff00) >> 8;
lsb = (jis & 0xff);
prod = (msb * 0xc0) + lsb;
binary += toBinary(prod, 0x1000);
info(prod + " ");
}
break;
case BINARY:
/* Byte mode */
/* Mode indicator */
switch (version) {
case 2:
binary += "10";
break;
case 3:
binary += "010";
break;
}
/* Character count indicator */
binary += toBinary(blockLength, 2 << version); /* version = 2..3 */
info("BYTE (" + blockLength + ") ");
/* Character representation */
for (i = 0; i < blockLength; i++) {
int lbyte = content.charAt(position + i);
binary += toBinary(lbyte, 0x80);
info(lbyte + " ");
}
break;
case ALPHANUM:
/* Alphanumeric mode */
/* Mode indicator */
switch (version) {
case 1:
binary += "1";
break;
case 2:
binary += "01";
break;
case 3:
binary += "001";
break;
}
/* Character count indicator */
binary += toBinary(blockLength, 2 << version); /* version = 1..3 */
info("ALPH (" + blockLength + ") ");
/* Character representation */
i = 0;
while (i < blockLength) {
first = positionOf(content.charAt(position + i), RHODIUM);
count = 1;
prod = first;
if (i + 1 < blockLength) {
if (inputMode[position + i + 1] == qrMode.ALPHANUM) {
second = positionOf(content.charAt(position + i + 1), RHODIUM);
count = 2;
prod = (first * 45) + second;
}
}
binary += toBinary(prod, 1 << (5 * count)); /* count = 1..2 */
info(prod + " ");
i += 2;
}
break;
case NUMERIC:
/* Numeric mode */
/* Mode indicator */
switch (version) {
case 1:
binary += "0";
break;
case 2:
binary += "00";
break;
case 3:
binary += "000";
break;
}
/* Character count indicator */
binary += toBinary(blockLength, 4 << version); /* version = 0..3 */
info("NUMB (" + blockLength + ") ");
/* Character representation */
i = 0;
while (i < blockLength) {
first = Character.getNumericValue(content.charAt(position + i));
count = 1;
prod = first;
if ((i + 1) < blockLength) {
if (inputMode[position + i + 1] == qrMode.NUMERIC) {
second = Character.getNumericValue(content.charAt(position + i + 1));
count = 2;
prod = (prod * 10) + second;
}
}
if ((i + 2) < blockLength) {
if (inputMode[position + i + 2] == qrMode.NUMERIC) {
third = Character.getNumericValue(content.charAt(position + i + 2));
count = 3;
prod = (prod * 10) + third;
}
}
binary += toBinary(prod, 1 << (3 * count)); /* count = 1..3 */
info(prod + " ");
i += 3;
}
break;
}
position += blockLength;
} while (position < content.length() - 1);
/* Add terminator */
switch(version) {
case 0:
binary += "000";
break;
case 1:
if (binary.length() < 37) {
binary += "00000";
}
break;
case 2:
if (binary.length() < 81) {
binary += "0000000";
}
break;
case 3:
if (binary.length() < 125) {
binary += "000000000";
}
break;
}
infoLine();
}
private void generateM1Symbol() {
int i, latch;
int bits_total, bits_left, remainder;
int data_codewords, ecc_codewords;
int[] data_blocks = new int[4];
int[] ecc_blocks = new int[3];
ReedSolomon rs = new ReedSolomon();
bits_total = 20;
latch = 0;
/* Manage last (4-bit) block */
bits_left = bits_total - binary.length();
if (bits_left <= 4) {
for (i = 0; i < bits_left; i++) {
binary += "0";
}
latch = 1;
}
if (latch == 0) {
/* Complete current byte */
remainder = 8 - (binary.length() % 8);
if (remainder == 8) {
remainder = 0;
}
for (i = 0; i < remainder; i++) {
binary += "0";
}
/* Add padding */
bits_left = bits_total - binary.length();
if (bits_left > 4) {
remainder = (bits_left - 4) / 8;
for (i = 0; i < remainder; i++) {
if ((i & 1) != 0) {
binary += "00010001";
} else {
binary += "11101100";
}
}
}
binary += "0000";
}
data_codewords = 3;
ecc_codewords = 2;
/* Copy data into codewords */
for (i = 0; i < (data_codewords - 1); i++) {
data_blocks[i] = 0;
if (binary.charAt(i * 8) == '1') {
data_blocks[i] += 0x80;
}
if (binary.charAt((i * 8) + 1) == '1') {
data_blocks[i] += 0x40;
}
if (binary.charAt((i * 8) + 2) == '1') {
data_blocks[i] += 0x20;
}
if (binary.charAt((i * 8) + 3) == '1') {
data_blocks[i] += 0x10;
}
if (binary.charAt((i * 8) + 4) == '1') {
data_blocks[i] += 0x08;
}
if (binary.charAt((i * 8) + 5) == '1') {
data_blocks[i] += 0x04;
}
if (binary.charAt((i * 8) + 6) == '1') {
data_blocks[i] += 0x02;
}
if (binary.charAt((i * 8) + 7) == '1') {
data_blocks[i] += 0x01;
}
}
data_blocks[2] = 0;
if (binary.charAt(16) == '1') {
data_blocks[2] += 0x08;
}
if (binary.charAt(17) == '1') {
data_blocks[2] += 0x04;
}
if (binary.charAt(18) == '1') {
data_blocks[2] += 0x02;
}
if (binary.charAt(19) == '1') {
data_blocks[2] += 0x01;
}
info("Codewords: ");
for (i = 0; i < data_codewords; i++) {
info(data_blocks[i] + " ");
}
infoLine();
/* Calculate Reed-Solomon error codewords */
rs.init_gf(0x11d);
rs.init_code(ecc_codewords, 0);
rs.encode(data_codewords, data_blocks);
for (i = 0; i < ecc_codewords; i++) {
ecc_blocks[i] = rs.getResult(i);
}
/* Add Reed-Solomon codewords to binary data */
for (i = 0; i < ecc_codewords; i++) {
binary += toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80);
}
}
private void generateM2Symbol(EccMode ecc_mode) {
int i;
int bits_total, bits_left, remainder;
int data_codewords, ecc_codewords;
int[] data_blocks = new int[6];
int[] ecc_blocks = new int[7];
ReedSolomon rs = new ReedSolomon();
bits_total = 40; // ecc_mode == EccMode.L
if (ecc_mode == EccMode.M) {
bits_total = 32;
}
/* Complete current byte */
remainder = 8 - (binary.length() % 8);
if (remainder == 8) {
remainder = 0;
}
for (i = 0; i < remainder; i++) {
binary += "0";
}
/* Add padding */
bits_left = bits_total - binary.length();
remainder = bits_left / 8;
for (i = 0; i < remainder; i++) {
if ((i & 1) != 0) {
binary += "00010001";
} else {
binary += "11101100";
}
}
data_codewords = 5;
ecc_codewords = 5; // ecc_mode == EccMode.L
if (ecc_mode == EccMode.M) {
data_codewords = 4;
ecc_codewords = 6;
}
/* Copy data into codewords */
for (i = 0; i < data_codewords; i++) {
data_blocks[i] = 0;
if (binary.charAt(i * 8) == '1') {
data_blocks[i] += 0x80;
}
if (binary.charAt((i * 8) + 1) == '1') {
data_blocks[i] += 0x40;
}
if (binary.charAt((i * 8) + 2) == '1') {
data_blocks[i] += 0x20;
}
if (binary.charAt((i * 8) + 3) == '1') {
data_blocks[i] += 0x10;
}
if (binary.charAt((i * 8) + 4) == '1') {
data_blocks[i] += 0x08;
}
if (binary.charAt((i * 8) + 5) == '1') {
data_blocks[i] += 0x04;
}
if (binary.charAt((i * 8) + 6) == '1') {
data_blocks[i] += 0x02;
}
if (binary.charAt((i * 8) + 7) == '1') {
data_blocks[i] += 0x01;
}
}
info("Codewords: ");
for (i = 0; i < data_codewords; i++) {
info(data_blocks[i] + " ");
}
infoLine();
/* Calculate Reed-Solomon error codewords */
rs.init_gf(0x11d);
rs.init_code(ecc_codewords, 0);
rs.encode(data_codewords, data_blocks);
for (i = 0; i < ecc_codewords; i++) {
ecc_blocks[i] = rs.getResult(i);
}
/* Add Reed-Solomon codewords to binary data */
for (i = 0; i < ecc_codewords; i++) {
binary += toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80);
}
}
private void generateM3Symbol(EccMode ecc_mode) {
int i, latch;
int bits_total, bits_left, remainder;
int data_codewords, ecc_codewords;
int[] data_blocks = new int[12];
int[] ecc_blocks = new int[12];
ReedSolomon rs = new ReedSolomon();
latch = 0;
bits_total = 84; // ecc_mode == EccMode.L
if (ecc_mode == EccMode.M) {
bits_total = 68;
}
/* Manage last (4-bit) block */
bits_left = bits_total - binary.length();
if (bits_left <= 4) {
for (i = 0; i < bits_left; i++) {
binary += "0";
}
latch = 1;
}
if (latch == 0) {
/* Complete current byte */
remainder = 8 - (binary.length() % 8);
if (remainder == 8) {
remainder = 0;
}
for (i = 0; i < remainder; i++) {
binary += "0";
}
/* Add padding */
bits_left = bits_total - binary.length();
if (bits_left > 4) {
remainder = (bits_left - 4) / 8;
for (i = 0; i < remainder; i++) {
if ((i & 1) != 0) {
binary += "00010001";
} else {
binary += "11101100";
}
}
}
binary += "0000";
}
data_codewords = 11;
ecc_codewords = 6; // ecc_mode == EccMode.L
if (ecc_mode == EccMode.M) {
data_codewords = 9;
ecc_codewords = 8;
}
/* Copy data into codewords */
for (i = 0; i < (data_codewords - 1); i++) {
data_blocks[i] = 0;
if (binary.charAt(i * 8) == '1') {
data_blocks[i] += 0x80;
}
if (binary.charAt((i * 8) + 1) == '1') {
data_blocks[i] += 0x40;
}
if (binary.charAt((i * 8) + 2) == '1') {
data_blocks[i] += 0x20;
}
if (binary.charAt((i * 8) + 3) == '1') {
data_blocks[i] += 0x10;
}
if (binary.charAt((i * 8) + 4) == '1') {
data_blocks[i] += 0x08;
}
if (binary.charAt((i * 8) + 5) == '1') {
data_blocks[i] += 0x04;
}
if (binary.charAt((i * 8) + 6) == '1') {
data_blocks[i] += 0x02;
}
if (binary.charAt((i * 8) + 7) == '1') {
data_blocks[i] += 0x01;
}
}
if (ecc_mode == EccMode.L) {
data_blocks[10] = 0;
if (binary.charAt(80) == '1') {
data_blocks[10] += 0x08;
}
if (binary.charAt(81) == '1') {
data_blocks[10] += 0x04;
}
if (binary.charAt(82) == '1') {
data_blocks[10] += 0x02;
}
if (binary.charAt(83) == '1') {
data_blocks[10] += 0x01;
}
}
if (ecc_mode == EccMode.M) {
data_blocks[8] = 0;
if (binary.charAt(64) == '1') {
data_blocks[8] += 0x08;
}
if (binary.charAt(65) == '1') {
data_blocks[8] += 0x04;
}
if (binary.charAt(66) == '1') {
data_blocks[8] += 0x02;
}
if (binary.charAt(67) == '1') {
data_blocks[8] += 0x01;
}
}
info("Codewords: ");
for (i = 0; i < data_codewords; i++) {
info(data_blocks[i] + " ");
}
infoLine();
/* Calculate Reed-Solomon error codewords */
rs.init_gf(0x11d);
rs.init_code(ecc_codewords, 0);
rs.encode(data_codewords, data_blocks);
for (i = 0; i < ecc_codewords; i++) {
ecc_blocks[i] = rs.getResult(i);
}
/* Add Reed-Solomon codewords to binary data */
for (i = 0; i < ecc_codewords; i++) {
binary += toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80);
}
}
private void generateM4Symbol(EccMode ecc_mode) {
int i;
int bits_total, bits_left, remainder;
int data_codewords, ecc_codewords;
int[] data_blocks = new int[17];
int[] ecc_blocks = new int[15];
ReedSolomon rs = new ReedSolomon();
bits_total = 128; // ecc_mode == EccMode.L
if (ecc_mode == EccMode.M) {
bits_total = 112;
}
if (ecc_mode == EccMode.Q) {
bits_total = 80;
}
/* Complete current byte */
remainder = 8 - (binary.length() % 8);
if (remainder == 8) {
remainder = 0;
}
for (i = 0; i < remainder; i++) {
binary += "0";
}
/* Add padding */
bits_left = bits_total - binary.length();
remainder = bits_left / 8;
for (i = 0; i < remainder; i++) {
if ((i & 1) != 0) {
binary += "00010001";
} else {
binary += "11101100";
}
}
data_codewords = 16;
ecc_codewords = 8; // ecc_mode == EccMode.L
if (ecc_mode == EccMode.M) {
data_codewords = 14;
ecc_codewords = 10;
}
if (ecc_mode == EccMode.Q) {
data_codewords = 10;
ecc_codewords = 14;
}
/* Copy data into codewords */
for (i = 0; i < data_codewords; i++) {
data_blocks[i] = 0;
if (binary.charAt(i * 8) == '1') {
data_blocks[i] += 0x80;
}
if (binary.charAt((i * 8) + 1) == '1') {
data_blocks[i] += 0x40;
}
if (binary.charAt((i * 8) + 2) == '1') {
data_blocks[i] += 0x20;
}
if (binary.charAt((i * 8) + 3) == '1') {
data_blocks[i] += 0x10;
}
if (binary.charAt((i * 8) + 4) == '1') {
data_blocks[i] += 0x08;
}
if (binary.charAt((i * 8) + 5) == '1') {
data_blocks[i] += 0x04;
}
if (binary.charAt((i * 8) + 6) == '1') {
data_blocks[i] += 0x02;
}
if (binary.charAt((i * 8) + 7) == '1') {
data_blocks[i] += 0x01;
}
}
info("Codewords: ");
for (i = 0; i < data_codewords; i++) {
info(data_blocks[i] + " ");
}
infoLine();
/* Calculate Reed-Solomon error codewords */
rs.init_gf(0x11d);
rs.init_code(ecc_codewords, 0);
rs.encode(data_codewords, data_blocks);
for (i = 0; i < ecc_codewords; i++) {
ecc_blocks[i] = rs.getResult(i);
}
/* Add Reed-Solomon codewords to binary data */
for (i = 0; i < ecc_codewords; i++) {
binary += toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80);
}
}
private void setupBitGrid(int size) {
int i, toggle = 1;
/* Add timing patterns */
for (i = 0; i < size; i++) {
if (toggle == 1) {
grid[i] = 0x21;
grid[(i * size)] = 0x21;
toggle = 0;
} else {
grid[i] = 0x20;
grid[(i * size)] = 0x20;
toggle = 1;
}
}
/* Add finder patterns */
placeFinderPattern(size, 0, 0);
/* Add separators */
for (i = 0; i < 7; i++) {
grid[(7 * size) + i] = 0x10;
grid[(i * size) + 7] = 0x10;
}
grid[(7 * size) + 7] = 0x10;
/* Reserve space for format information */
for (i = 0; i < 8; i++) {
grid[(8 * size) + i] += 0x20;
grid[(i * size) + 8] += 0x20;
}
grid[(8 * size) + 8] += 0x20;
}
private void placeFinderPattern(int size, int x, int y) {
int xp, yp;
int[] finder = {
1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 1,
1, 0, 1, 1, 1, 0, 1,
1, 0, 1, 1, 1, 0, 1,
1, 0, 1, 1, 1, 0, 1,
1, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1
};
for (xp = 0; xp < 7; xp++) {
for (yp = 0; yp < 7; yp++) {
if (finder[xp + (7 * yp)] == 1) {
grid[((yp + y) * size) + (xp + x)] = 0x11;
} else {
grid[((yp + y) * size) + (xp + x)] = 0x10;
}
}
}
}
private void populateBitGrid(int size) {
boolean goingUp = true;
int row = 0; /* right hand side */
int i, n, x, y;
n = binary.length();
y = size - 1;
i = 0;
do {
x = (size - 2) - (row * 2);
if ((grid[(y * size) + (x + 1)] & 0xf0) == 0) {
if (binary.charAt(i) == '1') {
grid[(y * size) + (x + 1)] = 0x01;
} else {
grid[(y * size) + (x + 1)] = 0x00;
}
i++;
}
if (i < n) {
if ((grid[(y * size) + x] & 0xf0) == 0) {
if (binary.charAt(i) == '1') {
grid[(y * size) + x] = 0x01;
} else {
grid[(y * size) + x] = 0x00;
}
i++;
}
}
if (goingUp) {
y--;
} else {
y++;
}
if (y == 0) {
/* reached the top */
row++;
y = 1;
goingUp = false;
}
if (y == size) {
/* reached the bottom */
row++;
y = size - 1;
goingUp = true;
}
} while (i < n);
}
private int applyBitmask(int size) {
int x, y;
int p;
int local_pattern;
int[] value = new int[8];
int best_val, best_pattern;
int bit;
int[] mask = new int[size * size];
eval = new int[size * size];
/* Perform data masking */
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
mask[(y * size) + x] = 0x00;
if ((grid[(y * size) + x] & 0xf0) == 0) {
if ((y & 1) == 0) {
mask[(y * size) + x] += 0x01;
}
if ((((y / 2) + (x / 3)) & 1) == 0) {
mask[(y * size) + x] += 0x02;
}
if (((((y * x) & 1) + ((y * x) % 3)) & 1) == 0) {
mask[(y * size) + x] += 0x04;
}
if (((((y + x) & 1) + ((y * x) % 3)) & 1) == 0) {
mask[(y * size) + x] += 0x08;
}
}
}
}
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
if ((grid[(y * size) + x] & 0x01) != 0) {
p = 0xff;
} else {
p = 0x00;
}
eval[(y * size) + x] = mask[(y * size) + x] ^ p;
}
}
/* Evaluate result */
for (local_pattern = 0; local_pattern < 4; local_pattern++) {
value[local_pattern] = evaluateBitmask(size, local_pattern);
}
best_pattern = 0;
best_val = value[0];
for (local_pattern = 1; local_pattern < 4; local_pattern++) {
if (value[local_pattern] > best_val) {
best_pattern = local_pattern;
best_val = value[local_pattern];
}
}
/* Apply mask */
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
bit = 0;
switch (best_pattern) {
case 0:
if ((mask[(y * size) + x] & 0x01) != 0) {
bit = 1;
}
break;
case 1:
if ((mask[(y * size) + x] & 0x02) != 0) {
bit = 1;
}
break;
case 2:
if ((mask[(y * size) + x] & 0x04) != 0) {
bit = 1;
}
break;
case 3:
if ((mask[(y * size) + x] & 0x08) != 0) {
bit = 1;
}
break;
}
if (bit == 1) {
if ((grid[(y * size) + x] & 0x01) != 0) {
grid[(y * size) + x] = 0x00;
} else {
grid[(y * size) + x] = 0x01;
}
}
}
}
return best_pattern;
}
private int evaluateBitmask(int size, int pattern) {
int sum1, sum2, i, filter = 0, retval;
switch (pattern) {
case 0:
filter = 0x01;
break;
case 1:
filter = 0x02;
break;
case 2:
filter = 0x04;
break;
case 3:
filter = 0x08;
break;
}
sum1 = 0;
sum2 = 0;
for (i = 1; i < size; i++) {
if ((eval[(i * size) + size - 1] & filter) != 0) {
sum1++;
}
if ((eval[((size - 1) * size) + i] & filter) != 0) {
sum2++;
}
}
if (sum1 <= sum2) {
retval = (sum1 * 16) + sum2;
} else {
retval = (sum2 * 16) + sum1;
}
return retval;
}
}