All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.org.okapibarcode.backend.QrCode Maven / Gradle / Ivy

/*
 * 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.nio.CharBuffer;
import java.nio.charset.Charset;

/**
 * 

Implements QR Code bar code symbology According to ISO/IEC 18004:2015. * *

The maximum capacity of a (version 40) QR Code symbol is 7089 numeric digits, * 4296 alphanumeric characters or 2953 bytes of data. QR Code symbols can also * be used to encode GS1 data. 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 QrCode extends Symbol { public enum EccLevel { 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_DATA_CODEWORDS_L = { 19, 34, 55, 80, 108, 136, 156, 194, 232, 274, 324, 370, 428, 461, 523, 589, 647, 721, 795, 861, 932, 1006, 1094, 1174, 1276, 1370, 1468, 1531, 1631, 1735, 1843, 1955, 2071, 2191, 2306, 2434, 2566, 2702, 2812, 2956 }; private static final int[] QR_DATA_CODEWORDS_M = { 16, 28, 44, 64, 86, 108, 124, 154, 182, 216, 254, 290, 334, 365, 415, 453, 507, 563, 627, 669, 714, 782, 860, 914, 1000, 1062, 1128, 1193, 1267, 1373, 1455, 1541, 1631, 1725, 1812, 1914, 1992, 2102, 2216, 2334 }; private static final int[] QR_DATA_CODEWORDS_Q = { 13, 22, 34, 48, 62, 76, 88, 110, 132, 154, 180, 206, 244, 261, 295, 325, 367, 397, 445, 485, 512, 568, 614, 664, 718, 754, 808, 871, 911, 985, 1033, 1115, 1171, 1231, 1286, 1354, 1426, 1502, 1582, 1666 }; private static final int[] QR_DATA_CODEWORDS_H = { 9, 16, 26, 36, 46, 60, 66, 86, 100, 122, 140, 158, 180, 197, 223, 253, 283, 313, 341, 385, 406, 442, 464, 514, 538, 596, 628, 661, 701, 745, 793, 845, 901, 961, 986, 1054, 1096, 1142, 1222, 1276 }; private static final int[] QR_BLOCKS_L = { 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25 }; private static final int[] QR_BLOCKS_M = { 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49 }; private static final int[] QR_BLOCKS_Q = { 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68 }; private static final int[] QR_BLOCKS_H = { 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81 }; private static final int[] QR_TOTAL_CODEWORDS = { 26, 44, 70, 100, 134, 172, 196, 242, 292, 346, 404, 466, 532, 581, 655, 733, 815, 901, 991, 1085, 1156, 1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185, 2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706 }; private static final int[] QR_SIZES = { 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 129, 133, 137, 141, 145, 149, 153, 157, 161, 165, 169, 173, 177 }; private static final int[] QR_ALIGN_LOOPSIZE = { 0, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7 }; private static final int[] QR_TABLE_E1 = { 6, 18, 0, 0, 0, 0, 0, 6, 22, 0, 0, 0, 0, 0, 6, 26, 0, 0, 0, 0, 0, 6, 30, 0, 0, 0, 0, 0, 6, 34, 0, 0, 0, 0, 0, 6, 22, 38, 0, 0, 0, 0, 6, 24, 42, 0, 0, 0, 0, 6, 26, 46, 0, 0, 0, 0, 6, 28, 50, 0, 0, 0, 0, 6, 30, 54, 0, 0, 0, 0, 6, 32, 58, 0, 0, 0, 0, 6, 34, 62, 0, 0, 0, 0, 6, 26, 46, 66, 0, 0, 0, 6, 26, 48, 70, 0, 0, 0, 6, 26, 50, 74, 0, 0, 0, 6, 30, 54, 78, 0, 0, 0, 6, 30, 56, 82, 0, 0, 0, 6, 30, 58, 86, 0, 0, 0, 6, 34, 62, 90, 0, 0, 0, 6, 28, 50, 72, 94, 0, 0, 6, 26, 50, 74, 98, 0, 0, 6, 30, 54, 78, 102, 0, 0, 6, 28, 54, 80, 106, 0, 0, 6, 32, 58, 84, 110, 0, 0, 6, 30, 58, 86, 114, 0, 0, 6, 34, 62, 90, 118, 0, 0, 6, 26, 50, 74, 98, 122, 0, 6, 30, 54, 78, 102, 126, 0, 6, 26, 52, 78, 104, 130, 0, 6, 30, 56, 82, 108, 134, 0, 6, 34, 60, 86, 112, 138, 0, 6, 30, 58, 86, 114, 142, 0, 6, 34, 62, 90, 118, 146, 0, 6, 30, 54, 78, 102, 126, 150, 6, 24, 50, 76, 102, 128, 154, 6, 28, 54, 80, 106, 132, 158, 6, 32, 58, 84, 110, 136, 162, 6, 26, 54, 82, 110, 138, 166, 6, 30, 58, 86, 114, 142, 170 }; private static final int[] QR_ANNEX_C = { /* Format information bit sequences */ 0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0, 0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976, 0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b, 0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed }; private static final int[] QR_ANNEX_D = { /* Version information bit sequences */ 0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, 0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9, 0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75, 0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64, 0x27541, 0x28c69 }; private int preferredVersion; private EccLevel preferredEccLevel = EccLevel.L; /** * Sets the preferred symbol size / version. 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: * *

* * * * * * * * * * * * * * * * * * * * * * * *
InputSymbol SizeInputSymbol Size
1 21 x 21 21 101 x 101
2 25 x 25 22 105 x 105
3 29 x 29 23 109 x 109
4 33 x 33 24 113 x 113
5 37 x 37 25 117 x 117
6 41 x 41 26 121 x 121
7 45 x 45 27 125 x 125
8 49 x 49 28 129 x 129
9 53 x 53 29 133 x 133
10 57 x 57 30 137 x 137
11 61 x 61 31 141 x 141
12 65 x 65 32 145 x 145
13 69 x 69 33 149 x 149
14 73 x 73 34 153 x 153
15 77 x 77 35 157 x 157
16 81 x 81 36 161 x 161
17 85 x 85 37 165 x 165
18 89 x 89 38 169 x 169
19 93 x 93 39 173 x 173
20 97 x 97 40 177 x 177
* * @param version the preferred symbol version */ public void setPreferredVersion(int version) { preferredVersion = version; } /** * Returns the preferred symbol version. * * @return the preferred symbol version */ public int getPreferredVersion() { return preferredVersion; } /** * Sets the preferred amount of symbol space allocated to error correction. This value may * be ignored if there is room for a higher error correction level. Levels are predefined * according to the following table: * * * * * * * * * *
ECC Level Error Correction CapacityRecovery 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 preferredEccLevel the preferred error correction level */ public void setPreferredEccLevel(EccLevel preferredEccLevel) { this.preferredEccLevel = preferredEccLevel; } /** * Returns the preferred amount of symbol space allocated to error correction. * * @return the preferred amount of symbol space allocated to error correction */ public EccLevel getPreferredEccLevel() { return this.preferredEccLevel; } @Override protected boolean gs1Supported() { return true; } @Override protected void encode() { int i, j; int est_binlen; EccLevel ecc_level; int max_cw; int targetCwCount, version, blocks; int size; int bitmask; boolean gs1 = (inputDataType == DataType.GS1); eciProcess(); // Get ECI mode if (eciMode == 20) { /* Shift-JIS encoding, use Kanji mode */ Charset c = Charset.forName("Shift_JIS"); inputData = new int[content.length()]; for (i = 0; i < inputData.length; i++) { CharBuffer buffer = CharBuffer.wrap(content, i, i + 1); byte[] bytes = c.encode(buffer).array(); int value = (bytes.length == 2 ? ((bytes[0] & 0xff) << 8) | (bytes[1] & 0xff) : bytes[0]); inputData[i] = value; } } else { /* inputData already initialized in eciProcess() */ } QrMode[] inputMode = new QrMode[inputData.length]; defineMode(inputMode, inputData); est_binlen = getBinaryLength(40, inputMode, inputData, gs1, eciMode); ecc_level = this.preferredEccLevel; switch (this.preferredEccLevel) { case L: default: max_cw = 2956; break; case M: max_cw = 2334; break; case Q: max_cw = 1666; break; case H: max_cw = 1276; break; } if (est_binlen > (8 * max_cw)) { throw new OkapiException("Input too long for selected error correction level"); } // ZINT NOTE: this block is different from the corresponding block of code in Zint; // it is simplified, but the simplification required that the applyOptimisation method // be changed to be free of side effects (by putting the optimized mode array into a // new array instead of modifying the existing array) version = 40; for (i = 39; i >= 0; i--) { int[] dataCodewords; switch (ecc_level) { case L: default: dataCodewords = QR_DATA_CODEWORDS_L; break; case M: dataCodewords = QR_DATA_CODEWORDS_M; break; case Q: dataCodewords = QR_DATA_CODEWORDS_Q; break; case H: dataCodewords = QR_DATA_CODEWORDS_H; break; } int proposedVersion = i + 1; int proposedBinLen = getBinaryLength(proposedVersion, inputMode, inputData, gs1, eciMode); if ((8 * dataCodewords[i]) >= proposedBinLen) { version = proposedVersion; est_binlen = proposedBinLen; } } inputMode = applyOptimisation(version, inputMode); // ZINT NOTE: end of block of code that is different // TODO: delete this // // autosize = 40; // for (i = 39; i >= 0; i--) { // switch (ecc_level) { // case L: // if ((8 * QR_DATA_CODEWORDS_L[i]) >= est_binlen) { // autosize = i + 1; // } // break; // case M: // if ((8 * QR_DATA_CODEWORDS_M[i]) >= est_binlen) { // autosize = i + 1; // } // break; // case Q: // if ((8 * QR_DATA_CODEWORDS_Q[i]) >= est_binlen) { // autosize = i + 1; // } // break; // case H: // if ((8 * QR_DATA_CODEWORDS_H[i]) >= est_binlen) { // autosize = i + 1; // } // break; // } // } // // // Now see if the optimized binary will fit in a smaller symbol. // canShrink = true; // // do { // if (autosize == 1) { // est_binlen = getBinaryLength(autosize, inputMode, inputData, gs1, eciMode); // TODO: added // canShrink = false; // } else { // est_binlen = getBinaryLength(autosize - 1, inputMode, inputData, gs1, eciMode); // // switch (ecc_level) { // case L: // if ((8 * QR_DATA_CODEWORDS_L[autosize - 2]) < est_binlen) { // canShrink = false; // } // break; // case M: // if ((8 * QR_DATA_CODEWORDS_M[autosize - 2]) < est_binlen) { // canShrink = false; // } // break; // case Q: // if ((8 * QR_DATA_CODEWORDS_Q[autosize - 2]) < est_binlen) { // canShrink = false; // } // break; // case H: // if ((8 * QR_DATA_CODEWORDS_H[autosize - 2]) < est_binlen) { // canShrink = false; // } // break; // } // // if (canShrink) { // // Optimization worked - data will fit in a smaller symbol // autosize--; // } else { // // Data did not fit in the smaller symbol, revert to original size // est_binlen = getBinaryLength(autosize, inputMode, inputData, gs1, eciMode); // } // } // } while (canShrink); // // version = autosize; if ((preferredVersion >= 1) && (preferredVersion <= 40)) { /* If the user has selected a larger symbol than the smallest available, then use the size the user has selected, and re-optimize for this symbol size. */ if (preferredVersion > version) { version = preferredVersion; est_binlen = getBinaryLength(preferredVersion, inputMode, inputData, gs1, eciMode); inputMode = applyOptimisation(version, inputMode); } if (preferredVersion < version) { throw new OkapiException("Input too long for selected symbol size"); } } /* Ensure maximum error correction capacity */ if (est_binlen <= (QR_DATA_CODEWORDS_M[version - 1] * 8)) { ecc_level = EccLevel.M; } if (est_binlen <= (QR_DATA_CODEWORDS_Q[version - 1] * 8)) { ecc_level = EccLevel.Q; } if (est_binlen <= (QR_DATA_CODEWORDS_H[version - 1] * 8)) { ecc_level = EccLevel.H; } targetCwCount = QR_DATA_CODEWORDS_L[version - 1]; blocks = QR_BLOCKS_L[version - 1]; switch (ecc_level) { case M: targetCwCount = QR_DATA_CODEWORDS_M[version - 1]; blocks = QR_BLOCKS_M[version - 1]; break; case Q: targetCwCount = QR_DATA_CODEWORDS_Q[version - 1]; blocks = QR_BLOCKS_Q[version - 1]; break; case H: targetCwCount = QR_DATA_CODEWORDS_H[version - 1]; blocks = QR_BLOCKS_H[version - 1]; break; } int[] datastream = new int[targetCwCount + 1]; int[] fullstream = new int[QR_TOTAL_CODEWORDS[version - 1] + 1]; qrBinary(datastream, version, targetCwCount, inputMode, inputData, gs1, eciMode, est_binlen); addEcc(fullstream, datastream, version, targetCwCount, blocks); size = QR_SIZES[version - 1]; int[] grid = new int[size * size]; infoLine("Version: " + version); infoLine("ECC Level: " + ecc_level.name()); setupGrid(grid, size, version); populateGrid(grid, size, fullstream, QR_TOTAL_CODEWORDS[version - 1]); if (version >= 7) { addVersionInfo(grid, size, version); } bitmask = applyBitmask(grid, size, ecc_level); infoLine("Mask Pattern: " + Integer.toBinaryString(bitmask)); addFormatInfo(grid, size, ecc_level, bitmask); readable = ""; pattern = new String[size]; row_count = size; row_height = new int[size]; for (i = 0; i < size; i++) { StringBuilder bin = new StringBuilder(size); 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; } } /** Place Kanji / Binary / Alphanumeric / Numeric values in inputMode. */ private static void defineMode(QrMode[] inputMode, int[] inputData) { int mlen; for (int i = 0; i < inputData.length; i++) { if (inputData[i] > 0xff) { inputMode[i] = QrMode.KANJI; } else { inputMode[i] = QrMode.BINARY; if (isAlpha(inputData[i])) { inputMode[i] = QrMode.ALPHANUM; } if (inputData[i] == FNC1) { inputMode[i] = QrMode.ALPHANUM; } if (isNumeric(inputData[i])) { inputMode[i] = QrMode.NUMERIC; } } } // TODO: uncomment // /* If less than 6 numeric digits together then don't use numeric mode */ // for (int i = 0; i < inputMode.length; i++) { // if (inputMode[i] == QrMode.NUMERIC) { // if (((i != 0) && (inputMode[i - 1] != QrMode.NUMERIC)) || (i == 0)) { // mlen = 0; // while (((mlen + i) < inputMode.length) && (inputMode[mlen + i] == QrMode.NUMERIC)) { // mlen++; // }; // if (mlen < 6) { // for (int j = 0; j < mlen; j++) { // inputMode[i + j] = QrMode.ALPHANUM; // } // } // } // } // } // // /* If less than 4 alphanumeric characters together then don't use alphanumeric mode */ // for (int i = 0; i < inputMode.length; i++) { // if (inputMode[i] == QrMode.ALPHANUM) { // if (((i != 0) && (inputMode[i - 1] != QrMode.ALPHANUM)) || (i == 0)) { // mlen = 0; // while (((mlen + i) < inputMode.length) && (inputMode[mlen + i] == QrMode.ALPHANUM)) { // mlen++; // }; // if (mlen < 4) { // for (int j = 0; j < mlen; j++) { // inputMode[i + j] = QrMode.BINARY; // } // } // } // } // } } /** Calculate the actual bit length of the proposed binary string. */ private static int getBinaryLength(int version, QrMode[] inputModeUnoptimized, int[] inputData, boolean gs1, int eciMode) { int i, j; QrMode currentMode; int inputLength = inputModeUnoptimized.length; int count = 0; int alphaLength; int percent = 0; // ZINT NOTE: in Zint, this call modifies the input mode array directly; here, we leave // the original array alone so that subsequent binary length checks don't irrevocably // optimize the mode array for the wrong QR Code version QrMode[] inputMode = applyOptimisation(version, inputModeUnoptimized); currentMode = QrMode.NULL; if (gs1) { count += 4; } if (eciMode != 3) { count += 12; } for (i = 0; i < inputLength; i++) { if (inputMode[i] != currentMode) { count += 4; switch (inputMode[i]) { case KANJI: count += tribus(version, 8, 10, 12); count += (blockLength(i, inputMode) * 13); break; case BINARY: count += tribus(version, 8, 16, 16); for (j = i; j < (i + blockLength(i, inputMode)); j++) { if (inputData[j] > 0xff) { count += 16; } else { count += 8; } } break; case ALPHANUM: count += tribus(version, 9, 11, 13); alphaLength = blockLength(i, inputMode); // In alphanumeric mode % becomes %% if (gs1) { for (j = i; j < (i + alphaLength); j++) { // TODO: need to do this only if in GS1 mode? or is the other code wrong? https://sourceforge.net/p/zint/tickets/104/#227b if (inputData[j] == '%') { percent++; } } } alphaLength += percent; switch (alphaLength % 2) { case 0: count += (alphaLength / 2) * 11; break; case 1: count += ((alphaLength - 1) / 2) * 11; count += 6; break; } break; case NUMERIC: count += tribus(version, 10, 12, 14); switch (blockLength(i, inputMode) % 3) { case 0: count += (blockLength(i, inputMode) / 3) * 10; break; case 1: count += ((blockLength(i, inputMode) - 1) / 3) * 10; count += 4; break; case 2: count += ((blockLength(i, inputMode) - 2) / 3) * 10; count += 7; break; } break; } currentMode = inputMode[i]; } } return count; } /** * Implements a custom optimization algorithm, because implementation of the algorithm * shown in Annex J.2 created LONGER binary sequences. */ private static QrMode[] applyOptimisation(int version, QrMode[] inputMode) { int inputLength = inputMode.length; int blockCount = 0; int i, j; QrMode currentMode = QrMode.NULL; for (i = 0; i < inputLength; i++) { if (inputMode[i] != currentMode) { currentMode = inputMode[i]; blockCount++; } } int[] blockLength = new int[blockCount]; QrMode[] blockMode = new QrMode[blockCount]; j = -1; currentMode = QrMode.NULL; for (i = 0; i < inputLength; i++) { if (inputMode[i] != currentMode) { j++; blockLength[j] = 1; blockMode[j] = inputMode[i]; currentMode = inputMode[i]; } else { blockLength[j]++; } } if (blockCount > 1) { // Search forward for (i = 0; i <= (blockCount - 2); i++) { if (blockMode[i] == QrMode.BINARY) { switch (blockMode[i + 1]) { case KANJI: if (blockLength[i + 1] < tribus(version, 4, 5, 6)) { blockMode[i + 1] = QrMode.BINARY; } break; case ALPHANUM: if (blockLength[i + 1] < tribus(version, 7, 8, 9)) { blockMode[i + 1] = QrMode.BINARY; } break; case NUMERIC: if (blockLength[i + 1] < tribus(version, 3, 4, 5)) { blockMode[i + 1] = QrMode.BINARY; } break; } } if ((blockMode[i] == QrMode.ALPHANUM) && (blockMode[i + 1] == QrMode.NUMERIC)) { if (blockLength[i + 1] < tribus(version, 6, 8, 10)) { blockMode[i + 1] = QrMode.ALPHANUM; } } } // Search backward for (i = blockCount - 1; i > 0; i--) { if (blockMode[i] == QrMode.BINARY) { switch (blockMode[i - 1]) { case KANJI: if (blockLength[i - 1] < tribus(version, 4, 5, 6)) { blockMode[i - 1] = QrMode.BINARY; } break; case ALPHANUM: if (blockLength[i - 1] < tribus(version, 7, 8, 9)) { blockMode[i - 1] = QrMode.BINARY; } break; case NUMERIC: if (blockLength[i - 1] < tribus(version, 3, 4, 5)) { blockMode[i - 1] = QrMode.BINARY; } break; } } if ((blockMode[i] == QrMode.ALPHANUM) && (blockMode[i - 1] == QrMode.NUMERIC)) { if (blockLength[i - 1] < tribus(version, 6, 8, 10)) { blockMode[i - 1] = QrMode.ALPHANUM; } } } } // ZINT NOTE: this method is different from the original Zint code in that it creates a // new array to hold the optimized values and returns it, rather than modifying the // original array; this allows this method to be called as many times as we want without // worrying about side effects QrMode[] optimized = new QrMode[inputMode.length]; j = 0; for (int block = 0; block < blockCount; block++) { currentMode = blockMode[block]; for (i = 0; i < blockLength[block]; i++) { optimized[j] = currentMode; j++; } } return optimized; } /** Find the length of the block starting from 'start'. */ private static int blockLength(int start, QrMode[] inputMode) { QrMode mode = inputMode[start]; int count = 0; int i = start; do { count++; } while ((i + count) < inputMode.length && inputMode[i + count] == mode); return count; } /** Choose from three numbers based on version. */ private static int tribus(int version, int a, int b, int c) { if (version < 10) { return a; } else if (version >= 10 && version <= 26) { return b; } else { return c; } } /** Returns true if input is in the Alphanumeric set (see Table J.1) */ private static boolean isAlpha(int c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c == ' ') || (c == '$') || (c == '%') || (c == '*') || (c == '+') || (c == '-') || (c == '.') || (c == '/') || (c == ':'); } /** Returns true if input is in the Numeric set (see Table J.1) */ private static boolean isNumeric(int c) { return (c >= '0' && c <= '9'); } /** Converts input data to a binary stream and adds padding. */ private void qrBinary(int[] datastream, int version, int target_binlen, QrMode[] inputMode, int[] inputData, boolean gs1, int eciMode, int est_binlen) { // TODO: make encodeInfo a StringBuilder, make this method static? int position = 0; int short_data_block_length, i; int padbits; int current_binlen, current_bytes; int toggle; QrMode data_block; StringBuilder binary = new StringBuilder(est_binlen + 12); if (gs1) { binary.append("0101"); /* FNC1 */ } if (eciMode != 3) { binary.append("0111"); /* ECI (Table 4) */ if (eciMode <= 127) { binaryAppend(eciMode, 8, binary); /* 000000 to 000127 */ } else if (eciMode <= 16383) { binaryAppend(0x8000 + eciMode, 16, binary); /* 000000 to 016383 */ } else { binaryAppend(0xC00000 + eciMode, 24, binary); /* 000000 to 999999 */ } } info("Encoding: "); do { data_block = inputMode[position]; short_data_block_length = 0; do { short_data_block_length++; } while (((short_data_block_length + position) < inputMode.length) && (inputMode[position + short_data_block_length] == data_block)); switch (data_block) { case KANJI: /* Kanji mode */ /* Mode indicator */ binary.append("1000"); /* Character count indicator */ binaryAppend(short_data_block_length, tribus(version, 8, 10, 12), binary); info("KNJI "); /* Character representation */ for (i = 0; i < short_data_block_length; i++) { int jis = inputData[position + i]; if (jis >= 0x8140 && jis <= 0x9ffc) { jis -= 0x8140; } else if (jis >= 0xe040 && jis <= 0xebbf) { jis -= 0xc140; } int prod = ((jis >> 8) * 0xc0) + (jis & 0xff); binaryAppend(prod, 13, binary); infoSpace(prod); } break; case BINARY: /* Byte mode */ /* Mode indicator */ binary.append("0100"); /* Character count indicator */ binaryAppend(short_data_block_length, tribus(version, 8, 16, 16), binary); info("BYTE "); /* Character representation */ for (i = 0; i < short_data_block_length; i++) { int b = inputData[position + i]; if (b == FNC1) { b = 0x1d; /* FNC1 */ } binaryAppend(b, 8, binary); infoSpace(b); } break; case ALPHANUM: /* Alphanumeric mode */ /* Mode indicator */ binary.append("0010"); /* If in GS1 mode, expand FNC1 -> '%' and expand '%' -> '%%' in a new array */ int percentCount = 0; if (gs1) { for (i = 0; i < short_data_block_length; i++) { if (inputData[position + i] == '%') { percentCount++; } } } int[] inputExpanded = new int[short_data_block_length + percentCount]; percentCount = 0; for (i = 0; i < short_data_block_length; i++) { int c = inputData[position + i]; if (c == FNC1) { inputExpanded[i + percentCount] = '%'; /* FNC1 */ } else { inputExpanded[i + percentCount] = c; if (gs1 && c == '%') { percentCount++; inputExpanded[i + percentCount] = c; } } } /* Character count indicator */ binaryAppend(inputExpanded.length, tribus(version, 9, 11, 13), binary); info("ALPH "); /* Character representation */ for (i = 0; i + 1 < inputExpanded.length; i += 2) { int first = positionOf((char) inputExpanded[i], RHODIUM); int second = positionOf((char) inputExpanded[i + 1], RHODIUM); int prod = (first * 45) + second; int count = 2; binaryAppend(prod, 1 + (5 * count), binary); infoSpace(prod); } if (inputExpanded.length % 2 != 0) { int first = positionOf((char) inputExpanded[inputExpanded.length - 1], RHODIUM); int prod = first; int count = 1; binaryAppend(prod, 1 + (5 * count), binary); infoSpace(prod); } break; case NUMERIC: /* Numeric mode */ /* Mode indicator */ binary.append("0001"); /* Character count indicator */ binaryAppend(short_data_block_length, tribus(version, 10, 12, 14), binary); info("NUMB "); /* Character representation */ i = 0; while (i < short_data_block_length) { int first = Character.getNumericValue(inputData[position + i]); int count = 1; int prod = first; if (i + 1 < short_data_block_length) { int second = Character.getNumericValue(inputData[position + i + 1]); count = 2; prod = (prod * 10) + second; if (i + 2 < short_data_block_length) { int third = Character.getNumericValue(inputData[position + i + 2]); count = 3; prod = (prod * 10) + third; } } binaryAppend(prod, 1 + (3 * count), binary); infoSpace(prod); i += count; } break; } position += short_data_block_length; } while (position < inputMode.length); infoLine(); /* Terminator */ binary.append("0000"); current_binlen = binary.length(); padbits = 8 - (current_binlen % 8); if (padbits == 8) { padbits = 0; } current_bytes = (current_binlen + padbits) / 8; /* Padding bits */ for (i = 0; i < padbits; i++) { binary.append('0'); } /* Put data into 8-bit codewords */ for (i = 0; i < current_bytes; i++) { datastream[i] = 0x00; for (int p = 0; p < 8; p++) { if (binary.charAt((i * 8) + p) == '1') { datastream[i] += (0x80 >> p); } } } /* Add pad codewords */ toggle = 0; for (i = current_bytes; i < target_binlen; i++) { if (toggle == 0) { datastream[i] = 0xec; toggle = 1; } else { datastream[i] = 0x11; toggle = 0; } } info("Codewords: "); for (i = 0; i < target_binlen; i++) { infoSpace(datastream[i]); } infoLine(); } private static void binaryAppend(int value, int length, StringBuilder binary) { int start = 0x01 << (length - 1); for (int i = 0; i < length; i++) { if ((value & (start >> i)) != 0) { binary.append('1'); } else { binary.append('0'); } } } /** Splits data into blocks, adds error correction and then interleaves the blocks and error correction data. */ private static void addEcc(int[] fullstream, int[] datastream, int version, int data_cw, int blocks) { int ecc_cw = QR_TOTAL_CODEWORDS[version - 1] - data_cw; int short_data_block_length = data_cw / blocks; int qty_long_blocks = data_cw % blocks; int qty_short_blocks = blocks - qty_long_blocks; int ecc_block_length = ecc_cw / blocks; int i, j, length_this_block, posn; int[] data_block = new int[short_data_block_length + 2]; int[] ecc_block = new int[ecc_block_length + 2]; int[] interleaved_data = new int[data_cw + 2]; int[] interleaved_ecc = new int[ecc_cw + 2]; posn = 0; for (i = 0; i < blocks; i++) { if (i < qty_short_blocks) { length_this_block = short_data_block_length; } else { length_this_block = short_data_block_length + 1; } for (j = 0; j < ecc_block_length; j++) { ecc_block[j] = 0; } for (j = 0; j < length_this_block; j++) { data_block[j] = datastream[posn + j]; } ReedSolomon rs = new ReedSolomon(); rs.init_gf(0x11d); rs.init_code(ecc_block_length, 0); rs.encode(length_this_block, data_block); for (j = 0; j < ecc_block_length; j++) { ecc_block[j] = rs.getResult(j); } for (j = 0; j < short_data_block_length; j++) { interleaved_data[(j * blocks) + i] = data_block[j]; } if (i >= qty_short_blocks) { interleaved_data[(short_data_block_length * blocks) + (i - qty_short_blocks)] = data_block[short_data_block_length]; } for (j = 0; j < ecc_block_length; j++) { interleaved_ecc[(j * blocks) + i] = ecc_block[ecc_block_length - j - 1]; } posn += length_this_block; } for (j = 0; j < data_cw; j++) { fullstream[j] = interleaved_data[j]; } for (j = 0; j < ecc_cw; j++) { fullstream[j + data_cw] = interleaved_ecc[j]; } } private static void setupGrid(int[] grid, int size, int version) { int i; boolean toggle = true; /* Add timing patterns */ for (i = 0; i < size; i++) { if (toggle) { grid[(6 * size) + i] = 0x21; grid[(i * size) + 6] = 0x21; toggle = false; } else { grid[(6 * size) + i] = 0x20; grid[(i * size) + 6] = 0x20; toggle = true; } } /* Add finder patterns */ placeFinder(grid, size, 0, 0); placeFinder(grid, size, 0, size - 7); placeFinder(grid, size, size - 7, 0); /* Add separators */ for (i = 0; i < 7; i++) { grid[(7 * size) + i] = 0x10; grid[(i * size) + 7] = 0x10; grid[(7 * size) + (size - 1 - i)] = 0x10; grid[(i * size) + (size - 8)] = 0x10; grid[((size - 8) * size) + i] = 0x10; grid[((size - 1 - i) * size) + 7] = 0x10; } grid[(7 * size) + 7] = 0x10; grid[(7 * size) + (size - 8)] = 0x10; grid[((size - 8) * size) + 7] = 0x10; /* Add alignment patterns */ if (version != 1) { /* Version 1 does not have alignment patterns */ int loopsize = QR_ALIGN_LOOPSIZE[version - 1]; for (int x = 0; x < loopsize; x++) { for (int y = 0; y < loopsize; y++) { int xcoord = QR_TABLE_E1[((version - 2) * 7) + x]; int ycoord = QR_TABLE_E1[((version - 2) * 7) + y]; if ((grid[(ycoord * size) + xcoord] & 0x10) == 0) { placeAlign(grid, size, xcoord, ycoord); } } } } /* Reserve space for format information */ for (i = 0; i < 8; i++) { grid[(8 * size) + i] += 0x20; grid[(i * size) + 8] += 0x20; grid[(8 * size) + (size - 1 - i)] = 0x20; grid[((size - 1 - i) * size) + 8] = 0x20; } grid[(8 * size) + 8] += 0x20; grid[((size - 1 - 7) * size) + 8] = 0x21; /* Dark Module from Figure 25 */ /* Reserve space for version information */ if (version >= 7) { for (i = 0; i < 6; i++) { grid[((size - 9) * size) + i] = 0x20; grid[((size - 10) * size) + i] = 0x20; grid[((size - 11) * size) + i] = 0x20; grid[(i * size) + (size - 9)] = 0x20; grid[(i * size) + (size - 10)] = 0x20; grid[(i * size) + (size - 11)] = 0x20; } } } private static void placeFinder(int[] grid, int size, int x, int y) { 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 (int xp = 0; xp < 7; xp++) { for (int 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 static void placeAlign(int[] grid, int size, int x, int y) { int[] alignment = { 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1 }; x -= 2; y -= 2; /* Input values represent centre of pattern */ for (int xp = 0; xp < 5; xp++) { for (int yp = 0; yp < 5; yp++) { if (alignment[xp + (5 * yp)] == 1) { grid[((yp + y) * size) + (xp + x)] = 0x11; } else { grid[((yp + y) * size) + (xp + x)] = 0x10; } } } } private static void populateGrid(int[] grid, int size, int[] fullstream, int cw) { boolean goingUp = true; int row = 0; /* right hand side */ int i, n, y; n = cw * 8; y = size - 1; i = 0; do { int x = (size - 2) - (row * 2); if (x < 6) { x--; /* skip over vertical timing pattern */ } if ((grid[(y * size) + (x + 1)] & 0xf0) == 0) { if (cwbit(fullstream, i)) { 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 (cwbit(fullstream, i)) { grid[(y * size) + x] = 0x01; } else { grid[(y * size) + x] = 0x00; } i++; } } if (goingUp) { y--; } else { y++; } if (y == -1) { /* reached the top */ row++; y = 0; goingUp = false; } if (y == size) { /* reached the bottom */ row++; y = size - 1; goingUp = true; } } while (i < n); } private static boolean cwbit(int[] fullstream, int i) { return ((fullstream[i / 8] & (0x80 >> (i % 8))) != 0); } private static int applyBitmask(int[] grid, int size, EccLevel ecc_level) { int x, y; char p; int pattern; int best_val, best_pattern; int[] penalty = new int[8]; byte[] mask = new byte[size * size]; byte[] eval = new byte[size * size]; /* Perform data masking */ for (x = 0; x < size; x++) { for (y = 0; y < size; y++) { mask[(y * size) + x] = 0x00; // all eight bit mask variants are encoded in the 8 bits of the bytes that make up the mask array if ((grid[(y * size) + x] & 0xf0) == 0) { // exclude areas not to be masked if (((y + x) & 1) == 0) { mask[(y * size) + x] += (byte) 0x01; } if ((y & 1) == 0) { mask[(y * size) + x] += (byte) 0x02; } if ((x % 3) == 0) { mask[(y * size) + x] += (byte) 0x04; } if (((y + x) % 3) == 0) { mask[(y * size) + x] += (byte) 0x08; } if ((((y / 2) + (x / 3)) & 1) == 0) { mask[(y * size) + x] += (byte) 0x10; } if ((((y * x) & 1) + ((y * x) % 3)) == 0) { mask[(y * size) + x] += (byte) 0x20; } if (((((y * x) & 1) + ((y * x) % 3)) & 1) == 0) { mask[(y * size) + x] += (byte) 0x40; } if (((((y + x) & 1) + ((y * x) % 3)) & 1) == 0) { mask[(y * size) + x] += (byte) 0x80; } } } } /* Apply data masks to grid, result in eval */ 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] = (byte) (mask[(y * size) + x] ^ p); } } /* Evaluate result */ for (pattern = 0; pattern < 8; pattern++) { addFormatInfoEval(eval, size, ecc_level, pattern); penalty[pattern] = evaluate(eval, size, pattern); } best_pattern = 0; best_val = penalty[0]; for (pattern = 1; pattern < 8; pattern++) { if (penalty[pattern] < best_val) { best_pattern = pattern; best_val = penalty[pattern]; } } /* Apply mask */ for (x = 0; x < size; x++) { for (y = 0; y < size; y++) { if ((mask[(y * size) + x] & (0x01 << best_pattern)) != 0) { if ((grid[(y * size) + x] & 0x01) != 0) { grid[(y * size) + x] = 0x00; } else { grid[(y * size) + x] = 0x01; } } } } return best_pattern; } /** Adds format information to eval. */ private static void addFormatInfoEval(byte[] eval, int size, EccLevel ecc_level, int pattern) { int format = pattern; int seq; int i; switch(ecc_level) { case L: format += 0x08; break; case Q: format += 0x18; break; case H: format += 0x10; break; } seq = QR_ANNEX_C[format]; for (i = 0; i < 6; i++) { eval[(i * size) + 8] = (byte) ((((seq >> i) & 0x01) != 0) ? (0x01 >> pattern) : 0x00); } for (i = 0; i < 8; i++) { eval[(8 * size) + (size - i - 1)] = (byte) ((((seq >> i) & 0x01) != 0) ? (0x01 >> pattern) : 0x00); } for (i = 0; i < 6; i++) { eval[(8 * size) + (5 - i)] = (byte) ((((seq >> (i + 9)) & 0x01) != 0) ? (0x01 >> pattern) : 0x00); } for (i = 0; i < 7; i++) { eval[(((size - 7) + i) * size) + 8] = (byte) ((((seq >> (i + 8)) & 0x01) != 0) ? (0x01 >> pattern) : 0x00); } eval[(7 * size) + 8] = (byte) ((((seq >> 6) & 0x01) != 0) ? (0x01 >> pattern) : 0x00); eval[(8 * size) + 8] = (byte) ((((seq >> 7) & 0x01) != 0) ? (0x01 >> pattern) : 0x00); eval[(8 * size) + 7] = (byte) ((((seq >> 8) & 0x01) != 0) ? (0x01 >> pattern) : 0x00); } private static int evaluate(byte[] eval, int size, int pattern) { int x, y, block, weight; int result = 0; int state; int p; int dark_mods; int percentage, k; int a, b, afterCount, beforeCount; byte[] local = new byte[size * size]; // all eight bit mask variants have been encoded in the 8 bits of the bytes // that make up the grid array; select them for evaluation according to the // desired pattern for (x = 0; x < size; x++) { for (y = 0; y < size; y++) { if ((eval[(y * size) + x] & (0x01 << pattern)) != 0) { local[(y * size) + x] = '1'; } else { local[(y * size) + x] = '0'; } } } /* Test 1: Adjacent modules in row/column in same colour */ /* Vertical */ for (x = 0; x < size; x++) { state = local[x]; block = 0; for (y = 0; y < size; y++) { if (local[(y * size) + x] == state) { block++; } else { if (block > 5) { result += (3 + (block - 5)); } block = 0; state = local[(y * size) + x]; } } if (block > 5) { result += (3 + (block - 5)); } } /* Horizontal */ for (y = 0; y < size; y++) { state = local[y * size]; block = 0; for (x = 0; x < size; x++) { if (local[(y * size) + x] == state) { block++; } else { if (block > 5) { result += (3 + (block - 5)); } block = 0; state = local[(y * size) + x]; } } if (block > 5) { result += (3 + (block - 5)); } } /* Test 2: Block of modules in same color */ for (x = 0; x < size - 1; x++) { for (y = 0; y < size - 1; y++) { if (((local[(y * size) + x] == local[((y + 1) * size) + x]) && (local[(y * size) + x] == local[(y * size) + (x + 1)])) && (local[(y * size) + x] == local[((y + 1) * size) + (x + 1)])) { result += 3; } } } /* Test 3: 1:1:3:1:1 ratio pattern in row/column */ /* Vertical */ for (x = 0; x < size; x++) { for (y = 0; y < (size - 7); y++) { p = 0; for (weight = 0; weight < 7; weight++) { if (local[((y + weight) * size) + x] == '1') { p += (0x40 >> weight); } } if (p == 0x5d) { /* Pattern found, check before and after */ beforeCount = 0; for (b = (y - 4); b < y; b++) { if (b < 0) { beforeCount++; } else { if (local[(b * size) + x] == '0') { beforeCount++; } else { beforeCount = 0; } } } afterCount = 0; for (a = (y + 7); a <= (y + 10); a++) { if (a >= size) { afterCount++; } else { if (local[(a * size) + x] == '0') { afterCount++; } else { afterCount = 0; } } } if ((beforeCount == 4) || (afterCount == 4)) { // Pattern is preceded or followed by light area 4 modules wide result += 40; } } } } /* Horizontal */ for (y = 0; y < size; y++) { for (x = 0; x < (size - 7); x++) { p = 0; for (weight = 0; weight < 7; weight++) { if (local[(y * size) + x + weight] == '1') { p += (0x40 >> weight); } } if (p == 0x5d) { /* Pattern found, check before and after */ beforeCount = 0; for (b = (x - 4); b < x; b++) { if (b < 0) { beforeCount++; } else { if (local[(y * size) + b] == '0') { beforeCount++; } else { beforeCount = 0; } } } afterCount = 0; for (a = (x + 7); a <= (x + 10); a++) { if (a >= size) { afterCount++; } else { if (local[(y * size) + a] == '0') { afterCount++; } else { afterCount = 0; } } } if ((beforeCount == 4) || (afterCount == 4)) { // Pattern is preceded or followed by light area 4 modules wide result += 40; } } } } /* Test 4: Proportion of dark modules in entire symbol */ dark_mods = 0; for (x = 0; x < size; x++) { for (y = 0; y < size; y++) { if (local[(y * size) + x] == '1') { dark_mods++; } } } percentage = 100 * (dark_mods / (size * size)); if (percentage <= 50) { k = ((100 - percentage) - 50) / 5; } else { k = (percentage - 50) / 5; } result += 10 * k; return result; } /* Adds format information to grid. */ private static void addFormatInfo(int[] grid, int size, EccLevel ecc_level, int pattern) { int format = pattern; int seq; int i; switch (ecc_level) { case L: format += 0x08; break; case Q: format += 0x18; break; case H: format += 0x10; break; } seq = QR_ANNEX_C[format]; for (i = 0; i < 6; i++) { grid[(i * size) + 8] += (seq >> i) & 0x01; } for (i = 0; i < 8; i++) { grid[(8 * size) + (size - i - 1)] += (seq >> i) & 0x01; } for (i = 0; i < 6; i++) { grid[(8 * size) + (5 - i)] += (seq >> (i + 9)) & 0x01; } for (i = 0; i < 7; i++) { grid[(((size - 7) + i) * size) + 8] += (seq >> (i + 8)) & 0x01; } grid[(7 * size) + 8] += (seq >> 6) & 0x01; grid[(8 * size) + 8] += (seq >> 7) & 0x01; grid[(8 * size) + 7] += (seq >> 8) & 0x01; } /** Adds version information. */ private static void addVersionInfo(int[] grid, int size, int version) { // TODO: Zint masks with 0x41 instead of 0x01; which is correct? https://sourceforge.net/p/zint/tickets/110/ int version_data = QR_ANNEX_D[version - 7]; for (int i = 0; i < 6; i++) { grid[((size - 11) * size) + i] += (version_data >> (i * 3)) & 0x01; grid[((size - 10) * size) + i] += (version_data >> ((i * 3) + 1)) & 0x01; grid[((size - 9) * size) + i] += (version_data >> ((i * 3) + 2)) & 0x01; grid[(i * size) + (size - 11)] += (version_data >> (i * 3)) & 0x01; grid[(i * size) + (size - 10)] += (version_data >> ((i * 3) + 1)) & 0x01; grid[(i * size) + (size - 9)] += (version_data >> ((i * 3) + 2)) & 0x01; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy