Dépôt officiel du code source de l'ERP OpenConcerto
Blame | Last modification | View Log | RSS feed
/*
* 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 <br>
* 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 <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class MicroQrCode extends Symbol {
public enum EccMode {
L, M, Q, H
}
private enum qrMode {
NULL, KANJI, BINARY, ALPHANUM, NUMERIC
}
/* 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 };
// user-specified values and settings
private int preferredVersion;
private EccMode preferredEccLevel = EccMode.L;
// internal state calculated when setContent() is called
private qrMode[] inputMode;
private StringBuilder binary;
private final int[] binaryCount = new int[4];
private int[] grid;
private int[] eval;
/**
* <p>
* 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.
*
* <table summary="Range of Micro QR symbol sizes">
* <tbody>
* <tr>
* <th>Input</th>
* <th>Version</th>
* <th>Symbol Size</th>
* </tr>
* <tr>
* <td>1</td>
* <td>M1</td>
* <td>11 x 11</td>
* </tr>
* <tr>
* <td>2</td>
* <td>M2</td>
* <td>13 x 13</td>
* </tr>
* <tr>
* <td>3</td>
* <td>M3</td>
* <td>15 x 15</td>
* </tr>
* <tr>
* <td>4</td>
* <td>M4</td>
* <td>17 x 17</td>
* </tr>
* </tbody>
* </table>
*
* @param version symbol size
*/
public void setPreferredVersion(final int version) {
if (version < 0 || version > 4) { // TODO: min 1
throw new IllegalArgumentException("Invalid version: " + version);
}
this.preferredVersion = version;
}
/**
* Returns the preferred symbol size.
*
* @return the preferred symbol size
* @see #setPreferredVersion(int)
*/
public int getPreferredVersion() {
return this.preferredVersion;
}
/**
* <p>
* Set the amount of symbol space allocated to error correction. Levels are predefined according
* to the following table:
*
* <table summary="Micro QR Error correction levels">
* <tbody>
* <tr>
* <th>ECC Level</th>
* <th>Error Correction Capacity</th>
* <th>Recovery Capacity</th>
* </tr>
* <tr>
* <td>L (default)</td>
* <td>Approx 20% of symbol</td>
* <td>Approx 7%</td>
* </tr>
* <tr>
* <td>M</td>
* <td>Approx 37% of symbol</td>
* <td>Approx 15%</td>
* </tr>
* <tr>
* <td>Q</td>
* <td>Approx 55% of symbol</td>
* <td>Approx 25%</td>
* </tr>
* <tr>
* <td>H</td>
* <td>Approx 65% of symbol</td>
* <td>Approx 30%</td>
* </tr>
* </tbody>
* </table>
*
* @param eccMode error correction level
*/
public void setEccMode(final EccMode eccMode) {
this.preferredEccLevel = eccMode;
}
/**
* Returns the preferred ECC mode (error correction level).
*
* @return the preferred ECC mode
* @see #setEccMode(EccMode)
*/
public EccMode getEccMode() {
return this.preferredEccLevel;
}
@Override
protected void encode() {
int i, j, size;
final boolean[] version_valid = new boolean[4];
int n_count, a_count;
EccMode ecc_level;
int version, autoversion;
int bitmask;
int format, format_full;
final StringBuilder bin = new StringBuilder();
boolean byteModeUsed;
boolean alphanumModeUsed;
boolean kanjiModeUsed;
if (this.content.length() > 35) {
throw new OkapiException("Input data too long");
}
inputCharCheck();
for (i = 0; i < 4; i++) {
version_valid[i] = true;
}
this.inputMode = new qrMode[40];
selectEncodingMode();
n_count = 0;
a_count = 0;
for (i = 0; i < this.content.length(); i++) {
if (this.content.charAt(i) >= '0' && this.content.charAt(i) <= '9') {
n_count++;
}
if (isAlphanumeric(this.content.charAt(i))) {
a_count++;
}
}
if (a_count == this.content.length()) {
/* All data can be encoded in Alphanumeric mode */
for (i = 0; i < this.content.length(); i++) {
this.inputMode[i] = qrMode.ALPHANUM;
}
}
if (n_count == this.content.length()) {
/* All data can be encoded in Numeric mode */
for (i = 0; i < this.content.length(); i++) {
this.inputMode[i] = qrMode.NUMERIC;
}
}
byteModeUsed = false;
alphanumModeUsed = false;
kanjiModeUsed = false;
for (i = 0; i < this.content.length(); i++) {
if (this.inputMode[i] == qrMode.BINARY) {
byteModeUsed = true;
}
if (this.inputMode[i] == qrMode.ALPHANUM) {
alphanumModeUsed = true;
}
if (this.inputMode[i] == qrMode.KANJI) {
kanjiModeUsed = true;
}
}
getBinaryLength();
/* Eliminate possible 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 (this.binaryCount[0] > 20) {
version_valid[0] = false;
}
if (this.binaryCount[1] > 40) {
version_valid[1] = false;
}
if (this.binaryCount[2] > 84) {
version_valid[2] = false;
}
if (this.binaryCount[3] > 128) {
throw new OkapiException("Input data too long");
}
/* Eliminate possible versions depending on error correction level specified */
ecc_level = this.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 (this.binaryCount[3] > 80) {
throw new OkapiException("Input data too long");
}
}
if (ecc_level == EccMode.M) {
version_valid[0] = false;
if (this.binaryCount[1] > 32) {
version_valid[1] = false;
}
if (this.binaryCount[2] > 68) {
version_valid[2] = false;
}
if (this.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 (this.preferredVersion >= 1 && this.preferredVersion <= 4) {
if (this.preferredVersion - 1 >= autoversion) {
version = this.preferredVersion - 1;
}
}
/* If there is enough unused space then increase the error correction level */
if (version == 3) {
if (this.binaryCount[3] <= 112) {
ecc_level = EccMode.M;
}
if (this.binaryCount[3] <= 80) {
ecc_level = EccMode.Q;
}
}
if (version == 2 && this.binaryCount[2] <= 68) {
ecc_level = EccMode.M;
}
if (version == 1 && this.binaryCount[1] <= 32) {
ecc_level = EccMode.M;
}
this.binary = new StringBuilder();
generateBinary(version);
if (this.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];
this.grid = new int[size * size];
for (i = 0; i < size; i++) {
for (j = 0; j < size; j++) {
this.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) {
this.grid[8 * size + 1] += 0x01;
}
if ((format_full & 0x2000) != 0) {
this.grid[8 * size + 2] += 0x01;
}
if ((format_full & 0x1000) != 0) {
this.grid[8 * size + 3] += 0x01;
}
if ((format_full & 0x800) != 0) {
this.grid[8 * size + 4] += 0x01;
}
if ((format_full & 0x400) != 0) {
this.grid[8 * size + 5] += 0x01;
}
if ((format_full & 0x200) != 0) {
this.grid[8 * size + 6] += 0x01;
}
if ((format_full & 0x100) != 0) {
this.grid[8 * size + 7] += 0x01;
}
if ((format_full & 0x80) != 0) {
this.grid[8 * size + 8] += 0x01;
}
if ((format_full & 0x40) != 0) {
this.grid[7 * size + 8] += 0x01;
}
if ((format_full & 0x20) != 0) {
this.grid[6 * size + 8] += 0x01;
}
if ((format_full & 0x10) != 0) {
this.grid[5 * size + 8] += 0x01;
}
if ((format_full & 0x08) != 0) {
this.grid[4 * size + 8] += 0x01;
}
if ((format_full & 0x04) != 0) {
this.grid[3 * size + 8] += 0x01;
}
if ((format_full & 0x02) != 0) {
this.grid[2 * size + 8] += 0x01;
}
if ((format_full & 0x01) != 0) {
this.grid[1 * size + 8] += 0x01;
}
this.readable = "";
this.pattern = new String[size];
this.row_count = size;
this.row_height = new int[size];
for (i = 0; i < size; i++) {
bin.setLength(0);
for (j = 0; j < size; j++) {
if ((this.grid[i * size + j] & 0x01) != 0) {
bin.append('1');
} else {
bin.append('0');
}
}
this.pattern[i] = bin2pat(bin);
this.row_height[i] = 1;
}
}
private void inputCharCheck() {
int qmarkBefore, qmarkAfter;
int i;
byte[] temp;
/* Check that input includes valid characters */
if (this.content.matches("[\u0000-\u00FF]+")) {
/* All characters in ISO 8859-1 */
return;
}
/* Otherwise check for Shift-JIS characters */
qmarkBefore = 0;
for (i = 0; i < this.content.length(); i++) {
if (this.content.charAt(i) == '?') {
qmarkBefore++;
}
}
try {
temp = this.content.getBytes("SJIS");
} catch (final 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(final 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;
final int length = this.content.length();
for (i = 0; i < length; i++) {
if (this.content.charAt(i) > 0xff) {
this.inputMode[i] = qrMode.KANJI;
} else {
this.inputMode[i] = qrMode.BINARY;
if (isAlphanumeric(this.content.charAt(i))) {
this.inputMode[i] = qrMode.ALPHANUM;
}
if (this.content.charAt(i) >= '0' && this.content.charAt(i) <= '9') {
this.inputMode[i] = qrMode.NUMERIC;
}
}
}
/* If less than 6 numeric digits together then don't use numeric mode */
for (i = 0; i < length; i++) {
if (this.inputMode[i] == qrMode.NUMERIC) {
if (i != 0 && this.inputMode[i - 1] != qrMode.NUMERIC || i == 0) {
mlen = 0;
while (mlen + i < length && this.inputMode[mlen + i] == qrMode.NUMERIC) {
mlen++;
}
if (mlen < 6) {
for (j = 0; j < mlen; j++) {
this.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 (this.inputMode[i] == qrMode.ALPHANUM) {
if (i != 0 && this.inputMode[i - 1] != qrMode.ALPHANUM || i == 0) {
mlen = 0;
while (mlen + i < length && this.inputMode[mlen + i] == qrMode.ALPHANUM) {
mlen++;
}
if (mlen < 6) {
for (j = 0; j < mlen; j++) {
this.inputMode[i + j] = qrMode.BINARY;
}
}
}
}
}
}
private boolean isAlphanumeric(final 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(final int data, int h) {
final StringBuilder binary = new StringBuilder();
for (; h != 0; h >>= 1) {
if ((data & h) != 0) {
binary.append('1');
} else {
binary.append('0');
}
}
return binary.toString();
}
private void getBinaryLength() {
int i;
qrMode currentMode = qrMode.NULL;
int blockLength;
/* Always include a terminator */
for (i = 0; i < 4; i++) {
this.binaryCount[i] = 0;
}
for (i = 0; i < this.content.length(); i++) {
if (currentMode != this.inputMode[i]) {
blockLength = 0;
do {
blockLength++;
} while (i + blockLength < this.content.length() && this.inputMode[i + blockLength] == this.inputMode[i]);
switch (this.inputMode[i]) {
case KANJI:
this.binaryCount[2] += 5 + blockLength * 13;
this.binaryCount[3] += 7 + blockLength * 13;
break;
case BINARY:
this.binaryCount[2] += 6 + blockLength * 8;
this.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;
}
this.binaryCount[1] += 4 + alphaLength;
this.binaryCount[2] += 6 + alphaLength;
this.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;
}
this.binaryCount[0] += 3 + numLength;
this.binaryCount[1] += 5 + numLength;
this.binaryCount[2] += 7 + numLength;
this.binaryCount[3] += 9 + numLength;
break;
}
currentMode = this.inputMode[i];
}
}
/* Add terminator */
if (this.binaryCount[1] < 37) {
this.binaryCount[1] += 5;
}
if (this.binaryCount[2] < 81) {
this.binaryCount[2] += 7;
}
if (this.binaryCount[3] < 125) {
this.binaryCount[3] += 9;
}
}
private void generateBinary(final 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 = this.inputMode[position];
blockLength = 0;
do {
blockLength++;
} while (blockLength + position < this.content.length() && this.inputMode[position + blockLength] == data_block);
switch (data_block) {
case KANJI:
/* Kanji mode */
/* Mode indicator */
switch (version) {
case 2:
this.binary.append("11");
break;
case 3:
this.binary.append("011");
break;
}
/* Character count indicator */
this.binary.append(toBinary(blockLength, 1 << version)); /* version = 2..3 */
info("KANJ (" + blockLength + ") ");
/* Character representation */
for (i = 0; i < blockLength; i++) {
oneChar = "";
oneChar += this.content.charAt(position + i);
/* Convert Unicode input to Shift-JIS */
try {
jisBytes = oneChar.getBytes("SJIS");
} catch (final UnsupportedEncodingException e) {
throw new OkapiException("Character encoding error");
}
jis = (jisBytes[0] & 0xFF) << 8;
if (jisBytes.length > 1) {
jis += jisBytes[1] & 0xFF;
}
if (jis > 0x9fff) {
jis -= 0xc140;
} else {
jis -= 0x8140;
}
msb = (jis & 0xff00) >> 8;
lsb = jis & 0xff;
prod = msb * 0xc0 + lsb;
this.binary.append(toBinary(prod, 0x1000));
infoSpace(prod);
}
break;
case BINARY:
/* Byte mode */
/* Mode indicator */
switch (version) {
case 2:
this.binary.append("10");
break;
case 3:
this.binary.append("010");
break;
}
/* Character count indicator */
this.binary.append(toBinary(blockLength, 2 << version)); /* version = 2..3 */
info("BYTE (" + blockLength + ") ");
/* Character representation */
for (i = 0; i < blockLength; i++) {
final int lbyte = this.content.charAt(position + i);
this.binary.append(toBinary(lbyte, 0x80));
infoSpace(lbyte);
}
break;
case ALPHANUM:
/* Alphanumeric mode */
/* Mode indicator */
switch (version) {
case 1:
this.binary.append("1");
break;
case 2:
this.binary.append("01");
break;
case 3:
this.binary.append("001");
break;
}
/* Character count indicator */
this.binary.append(toBinary(blockLength, 2 << version)); /* version = 1..3 */
info("ALPH (" + blockLength + ") ");
/* Character representation */
i = 0;
while (i < blockLength) {
first = positionOf(this.content.charAt(position + i), RHODIUM);
count = 1;
prod = first;
if (i + 1 < blockLength) {
if (this.inputMode[position + i + 1] == qrMode.ALPHANUM) {
second = positionOf(this.content.charAt(position + i + 1), RHODIUM);
count = 2;
prod = first * 45 + second;
}
}
this.binary.append(toBinary(prod, 1 << 5 * count)); /* count = 1..2 */
infoSpace(prod);
i += 2;
}
break;
case NUMERIC:
/* Numeric mode */
/* Mode indicator */
switch (version) {
case 1:
this.binary.append("0");
break;
case 2:
this.binary.append("00");
break;
case 3:
this.binary.append("000");
break;
}
/* Character count indicator */
this.binary.append(toBinary(blockLength, 4 << version)); /* version = 0..3 */
info("NUMB (" + blockLength + ") ");
/* Character representation */
i = 0;
while (i < blockLength) {
first = Character.getNumericValue(this.content.charAt(position + i));
count = 1;
prod = first;
if (i + 1 < blockLength) {
if (this.inputMode[position + i + 1] == qrMode.NUMERIC) {
second = Character.getNumericValue(this.content.charAt(position + i + 1));
count = 2;
prod = prod * 10 + second;
}
}
if (i + 2 < blockLength) {
if (this.inputMode[position + i + 2] == qrMode.NUMERIC) {
third = Character.getNumericValue(this.content.charAt(position + i + 2));
count = 3;
prod = prod * 10 + third;
}
}
this.binary.append(toBinary(prod, 1 << 3 * count)); /* count = 1..3 */
infoSpace(prod);
i += 3;
}
break;
}
position += blockLength;
} while (position < this.content.length() - 1);
/* Add terminator */
switch (version) {
case 0:
this.binary.append("000");
break;
case 1:
if (this.binary.length() < 37) {
this.binary.append("00000");
}
break;
case 2:
if (this.binary.length() < 81) {
this.binary.append("0000000");
}
break;
case 3:
if (this.binary.length() < 125) {
this.binary.append("000000000");
}
break;
}
infoLine();
}
private void generateM1Symbol() {
int i, latch;
int bits_total, bits_left, remainder;
int data_codewords, ecc_codewords;
final int[] data_blocks = new int[4];
final int[] ecc_blocks = new int[3];
final ReedSolomon rs = new ReedSolomon();
bits_total = 20;
latch = 0;
/* Manage last (4-bit) block */
bits_left = bits_total - this.binary.length();
if (bits_left <= 4) {
for (i = 0; i < bits_left; i++) {
this.binary.append("0");
}
latch = 1;
}
if (latch == 0) {
/* Complete current byte */
remainder = 8 - this.binary.length() % 8;
if (remainder == 8) {
remainder = 0;
}
for (i = 0; i < remainder; i++) {
this.binary.append("0");
}
/* Add padding */
bits_left = bits_total - this.binary.length();
if (bits_left > 4) {
remainder = (bits_left - 4) / 8;
for (i = 0; i < remainder; i++) {
if ((i & 1) != 0) {
this.binary.append("00010001");
} else {
this.binary.append("11101100");
}
}
}
this.binary.append("0000");
}
data_codewords = 3;
ecc_codewords = 2;
/* Copy data into codewords */
for (i = 0; i < data_codewords - 1; i++) {
data_blocks[i] = 0;
if (this.binary.charAt(i * 8) == '1') {
data_blocks[i] += 0x80;
}
if (this.binary.charAt(i * 8 + 1) == '1') {
data_blocks[i] += 0x40;
}
if (this.binary.charAt(i * 8 + 2) == '1') {
data_blocks[i] += 0x20;
}
if (this.binary.charAt(i * 8 + 3) == '1') {
data_blocks[i] += 0x10;
}
if (this.binary.charAt(i * 8 + 4) == '1') {
data_blocks[i] += 0x08;
}
if (this.binary.charAt(i * 8 + 5) == '1') {
data_blocks[i] += 0x04;
}
if (this.binary.charAt(i * 8 + 6) == '1') {
data_blocks[i] += 0x02;
}
if (this.binary.charAt(i * 8 + 7) == '1') {
data_blocks[i] += 0x01;
}
}
data_blocks[2] = 0;
if (this.binary.charAt(16) == '1') {
data_blocks[2] += 0x08;
}
if (this.binary.charAt(17) == '1') {
data_blocks[2] += 0x04;
}
if (this.binary.charAt(18) == '1') {
data_blocks[2] += 0x02;
}
if (this.binary.charAt(19) == '1') {
data_blocks[2] += 0x01;
}
info("Codewords: ");
for (i = 0; i < data_codewords; i++) {
infoSpace(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++) {
this.binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
}
}
private void generateM2Symbol(final EccMode ecc_mode) {
int i;
int bits_total, bits_left, remainder;
int data_codewords, ecc_codewords;
final int[] data_blocks = new int[6];
final int[] ecc_blocks = new int[7];
final ReedSolomon rs = new ReedSolomon();
bits_total = 40; // ecc_mode == EccMode.L
if (ecc_mode == EccMode.M) {
bits_total = 32;
}
/* Complete current byte */
remainder = 8 - this.binary.length() % 8;
if (remainder == 8) {
remainder = 0;
}
for (i = 0; i < remainder; i++) {
this.binary.append("0");
}
/* Add padding */
bits_left = bits_total - this.binary.length();
remainder = bits_left / 8;
for (i = 0; i < remainder; i++) {
if ((i & 1) != 0) {
this.binary.append("00010001");
} else {
this.binary.append("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 (this.binary.charAt(i * 8) == '1') {
data_blocks[i] += 0x80;
}
if (this.binary.charAt(i * 8 + 1) == '1') {
data_blocks[i] += 0x40;
}
if (this.binary.charAt(i * 8 + 2) == '1') {
data_blocks[i] += 0x20;
}
if (this.binary.charAt(i * 8 + 3) == '1') {
data_blocks[i] += 0x10;
}
if (this.binary.charAt(i * 8 + 4) == '1') {
data_blocks[i] += 0x08;
}
if (this.binary.charAt(i * 8 + 5) == '1') {
data_blocks[i] += 0x04;
}
if (this.binary.charAt(i * 8 + 6) == '1') {
data_blocks[i] += 0x02;
}
if (this.binary.charAt(i * 8 + 7) == '1') {
data_blocks[i] += 0x01;
}
}
info("Codewords: ");
for (i = 0; i < data_codewords; i++) {
infoSpace(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++) {
this.binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
}
}
private void generateM3Symbol(final EccMode ecc_mode) {
int i, latch;
int bits_total, bits_left, remainder;
int data_codewords, ecc_codewords;
final int[] data_blocks = new int[12];
final int[] ecc_blocks = new int[12];
final 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 - this.binary.length();
if (bits_left <= 4) {
for (i = 0; i < bits_left; i++) {
this.binary.append("0");
}
latch = 1;
}
if (latch == 0) {
/* Complete current byte */
remainder = 8 - this.binary.length() % 8;
if (remainder == 8) {
remainder = 0;
}
for (i = 0; i < remainder; i++) {
this.binary.append("0");
}
/* Add padding */
bits_left = bits_total - this.binary.length();
if (bits_left > 4) {
remainder = (bits_left - 4) / 8;
for (i = 0; i < remainder; i++) {
if ((i & 1) != 0) {
this.binary.append("00010001");
} else {
this.binary.append("11101100");
}
}
}
this.binary.append("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 (this.binary.charAt(i * 8) == '1') {
data_blocks[i] += 0x80;
}
if (this.binary.charAt(i * 8 + 1) == '1') {
data_blocks[i] += 0x40;
}
if (this.binary.charAt(i * 8 + 2) == '1') {
data_blocks[i] += 0x20;
}
if (this.binary.charAt(i * 8 + 3) == '1') {
data_blocks[i] += 0x10;
}
if (this.binary.charAt(i * 8 + 4) == '1') {
data_blocks[i] += 0x08;
}
if (this.binary.charAt(i * 8 + 5) == '1') {
data_blocks[i] += 0x04;
}
if (this.binary.charAt(i * 8 + 6) == '1') {
data_blocks[i] += 0x02;
}
if (this.binary.charAt(i * 8 + 7) == '1') {
data_blocks[i] += 0x01;
}
}
if (ecc_mode == EccMode.L) {
data_blocks[10] = 0;
if (this.binary.charAt(80) == '1') {
data_blocks[10] += 0x08;
}
if (this.binary.charAt(81) == '1') {
data_blocks[10] += 0x04;
}
if (this.binary.charAt(82) == '1') {
data_blocks[10] += 0x02;
}
if (this.binary.charAt(83) == '1') {
data_blocks[10] += 0x01;
}
}
if (ecc_mode == EccMode.M) {
data_blocks[8] = 0;
if (this.binary.charAt(64) == '1') {
data_blocks[8] += 0x08;
}
if (this.binary.charAt(65) == '1') {
data_blocks[8] += 0x04;
}
if (this.binary.charAt(66) == '1') {
data_blocks[8] += 0x02;
}
if (this.binary.charAt(67) == '1') {
data_blocks[8] += 0x01;
}
}
info("Codewords: ");
for (i = 0; i < data_codewords; i++) {
infoSpace(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++) {
this.binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
}
}
private void generateM4Symbol(final EccMode ecc_mode) {
int i;
int bits_total, bits_left, remainder;
int data_codewords, ecc_codewords;
final int[] data_blocks = new int[17];
final int[] ecc_blocks = new int[15];
final 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 - this.binary.length() % 8;
if (remainder == 8) {
remainder = 0;
}
for (i = 0; i < remainder; i++) {
this.binary.append("0");
}
/* Add padding */
bits_left = bits_total - this.binary.length();
remainder = bits_left / 8;
for (i = 0; i < remainder; i++) {
if ((i & 1) != 0) {
this.binary.append("00010001");
} else {
this.binary.append("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 (this.binary.charAt(i * 8) == '1') {
data_blocks[i] += 0x80;
}
if (this.binary.charAt(i * 8 + 1) == '1') {
data_blocks[i] += 0x40;
}
if (this.binary.charAt(i * 8 + 2) == '1') {
data_blocks[i] += 0x20;
}
if (this.binary.charAt(i * 8 + 3) == '1') {
data_blocks[i] += 0x10;
}
if (this.binary.charAt(i * 8 + 4) == '1') {
data_blocks[i] += 0x08;
}
if (this.binary.charAt(i * 8 + 5) == '1') {
data_blocks[i] += 0x04;
}
if (this.binary.charAt(i * 8 + 6) == '1') {
data_blocks[i] += 0x02;
}
if (this.binary.charAt(i * 8 + 7) == '1') {
data_blocks[i] += 0x01;
}
}
info("Codewords: ");
for (i = 0; i < data_codewords; i++) {
infoSpace(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++) {
this.binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
}
}
private void setupBitGrid(final int size) {
int i, toggle = 1;
/* Add timing patterns */
for (i = 0; i < size; i++) {
if (toggle == 1) {
this.grid[i] = 0x21;
this.grid[i * size] = 0x21;
toggle = 0;
} else {
this.grid[i] = 0x20;
this.grid[i * size] = 0x20;
toggle = 1;
}
}
/* Add finder patterns */
placeFinderPattern(size, 0, 0);
/* Add separators */
for (i = 0; i < 7; i++) {
this.grid[7 * size + i] = 0x10;
this.grid[i * size + 7] = 0x10;
}
this.grid[7 * size + 7] = 0x10;
/* Reserve space for format information */
for (i = 0; i < 8; i++) {
this.grid[8 * size + i] += 0x20;
this.grid[i * size + 8] += 0x20;
}
this.grid[8 * size + 8] += 0x20;
}
private void placeFinderPattern(final int size, final int x, final int y) {
int xp, yp;
final 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) {
this.grid[(yp + y) * size + xp + x] = 0x11;
} else {
this.grid[(yp + y) * size + xp + x] = 0x10;
}
}
}
}
private void populateBitGrid(final int size) {
boolean goingUp = true;
int row = 0; /* right hand side */
int i, n, x, y;
n = this.binary.length();
y = size - 1;
i = 0;
do {
x = size - 2 - row * 2;
if ((this.grid[y * size + x + 1] & 0xf0) == 0) {
if (this.binary.charAt(i) == '1') {
this.grid[y * size + x + 1] = 0x01;
} else {
this.grid[y * size + x + 1] = 0x00;
}
i++;
}
if (i < n) {
if ((this.grid[y * size + x] & 0xf0) == 0) {
if (this.binary.charAt(i) == '1') {
this.grid[y * size + x] = 0x01;
} else {
this.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(final int size) {
int x, y;
int p;
int local_pattern;
final int[] value = new int[8];
int best_val, best_pattern;
int bit;
final int[] mask = new int[size * size];
this.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 ((this.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 ((this.grid[y * size + x] & 0x01) != 0) {
p = 0xff;
} else {
p = 0x00;
}
this.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 ((this.grid[y * size + x] & 0x01) != 0) {
this.grid[y * size + x] = 0x00;
} else {
this.grid[y * size + x] = 0x01;
}
}
}
}
return best_pattern;
}
private int evaluateBitmask(final int size, final 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 ((this.eval[i * size + size - 1] & filter) != 0) {
sum1++;
}
if ((this.eval[(size - 1) * size + i] & filter) != 0) {
sum2++;
}
}
if (sum1 <= sum2) {
retval = sum1 * 16 + sum2;
} else {
retval = sum2 * 16 + sum1;
}
return retval;
}
}