Added and updated many comments, almost all at the member level (rarely within functions), some with original wording, some synchronizing with the main qrcodegen project.

pull/134/head
Project Nayuki 5 years ago
parent 960b9cd32d
commit 6d6e0f3fde

@ -27,24 +27,20 @@ import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
/** // An appendable sequence of bits (0s and 1s), mainly used by QrSegment.
* An appendable sequence of bits (0s and 1s). Mainly used by {@link QrSegment}.
*/
final class BitBuffer { final class BitBuffer {
/*---- Fields ----*/ /*---- Fields ----*/
int[] data; int[] data; // In each 32-bit word, bits are filled from top down.
int bitLength; int bitLength; // Always non-negative.
/*---- Constructor ----*/ /*---- Constructor ----*/
/** // Creates an empty bit buffer.
* Constructs an empty bit buffer (length 0).
*/
public BitBuffer() { public BitBuffer() {
data = new int[64]; data = new int[64];
bitLength = 0; bitLength = 0;
@ -54,10 +50,7 @@ final class BitBuffer {
/*---- Methods ----*/ /*---- Methods ----*/
/** // Returns the bit at the given index, yielding 0 or 1.
* Returns the length of this sequence, which is a non-negative value.
* @return the length of this sequence
*/
public int getBit(int index) { public int getBit(int index) {
if (index < 0 || index >= bitLength) if (index < 0 || index >= bitLength)
throw new IndexOutOfBoundsException(); throw new IndexOutOfBoundsException();
@ -65,11 +58,8 @@ final class BitBuffer {
} }
/** // Returns a new array representing this buffer's bits packed into
* Returns an array representing this buffer's bits packed into bytes // bytes in big endian. The current bit length must be a multiple of 8.
* in big endian. The current bit length must be a multiple of 8.
* @return a new byte array (not {@code null}) representing this bit sequence
*/
public byte[] getBytes() { public byte[] getBytes() {
if (bitLength % 8 != 0) if (bitLength % 8 != 0)
throw new IllegalStateException("Data is not a whole number of bytes"); throw new IllegalStateException("Data is not a whole number of bytes");
@ -80,15 +70,8 @@ final class BitBuffer {
} }
/** // Appends the given number of low-order bits of the given value
* Appends the specified number of low-order bits of the specified value to this // to this buffer. Requires 0 <= len <= 31 and 0 <= val < 2^len.
* buffer. Requires 0 &#x2264; len &#x2264; 31 and 0 &#x2264; val &lt; 2<sup>len</sup>.
* @param val the value to append
* @param len the number of low-order bits in the value to take
* @throws IllegalArgumentException if the value or number of bits is out of range
* @throws IllegalStateException if appending the data
* would make bitLength exceed Integer.MAX_VALUE
*/
public void appendBits(int val, int len) { public void appendBits(int val, int len) {
if (len < 0 || len > 31 || val >>> len != 0) if (len < 0 || len > 31 || val >>> len != 0)
throw new IllegalArgumentException("Value out of range"); throw new IllegalArgumentException("Value out of range");
@ -114,14 +97,8 @@ final class BitBuffer {
} }
/** // Appends to this buffer the sequence of bits represented by the given
* Appends the specified sequence of bits to this buffer. // word array and given bit length. Requires 0 <= len <= 32 * vals.length.
* Requires 0 &#x2264; len &#x2264; 32 &#xD7; vals.length.
* @param vals the sequence of bits to append (not {@code null})
* @param len the number of prefix bits to read from the array
* @throws IllegalStateException if appending the data
* would make bitLength exceed Integer.MAX_VALUE
*/
public void appendBits(int[] vals, int len) { public void appendBits(int[] vals, int len) {
Objects.requireNonNull(vals); Objects.requireNonNull(vals);
if (len == 0) if (len == 0)

@ -215,9 +215,8 @@ public final class QrCode {
* &#x2212;1), the resulting object still has a mask value between 0 and 7. */ * &#x2212;1), the resulting object still has a mask value between 0 and 7. */
public final int mask; public final int mask;
// Private grid of modules/pixels: // Private grid of modules of this QR Code, packed tightly into bits.
// Immutable after constructor finishes. Accessed through getModule().
// The modules of this QR Code. Immutable after constructor finishes. Accessed through getModule().
private final int[] modules; private final int[] modules;
@ -424,8 +423,8 @@ public final class QrCode {
} }
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire // Draws the given sequence of 8-bit codewords (data and error correction)
// data area of this QR Code symbol. Function modules need to be marked off before this is called. // onto the entire data area of this QR Code, based on the given bit indexes.
private void drawCodewords(int[] dataOutputBitIndexes, byte[] allCodewords) { private void drawCodewords(int[] dataOutputBitIndexes, byte[] allCodewords) {
Objects.requireNonNull(dataOutputBitIndexes); Objects.requireNonNull(dataOutputBitIndexes);
Objects.requireNonNull(allCodewords); Objects.requireNonNull(allCodewords);
@ -443,7 +442,7 @@ public final class QrCode {
// The function modules must be marked and the codeword bits must be drawn // The function modules must be marked and the codeword bits must be drawn
// before masking. Due to the arithmetic of XOR, calling applyMask() with // before masking. Due to the arithmetic of XOR, calling applyMask() with
// the same mask value a second time will undo the mask. A final well-formed // the same mask value a second time will undo the mask. A final well-formed
// QR Code symbol needs exactly one (not zero, two, etc.) mask applied. // QR Code needs exactly one (not zero, two, etc.) mask applied.
private void applyMask(int[] mask) { private void applyMask(int[] mask) {
if (mask.length != modules.length) if (mask.length != modules.length)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
@ -453,7 +452,7 @@ public final class QrCode {
// A messy helper function for the constructor. This QR Code must be in an unmasked state when this // A messy helper function for the constructor. This QR Code must be in an unmasked state when this
// method is called. The given argument is the requested mask, which is -1 for auto or 0 to 7 for fixed. // method is called. The 'mask' argument is the requested mask, which is -1 for auto or 0 to 7 for fixed.
// This method applies and returns the actual mask chosen, from 0 to 7. // This method applies and returns the actual mask chosen, from 0 to 7.
private int handleConstructorMasking(int[][] masks, int mask) { private int handleConstructorMasking(int[][] masks, int mask) {
if (mask == -1) { // Automatically choose best mask if (mask == -1) { // Automatically choose best mask

@ -24,23 +24,28 @@
package io.nayuki.fastqrcodegen; package io.nayuki.fastqrcodegen;
// Stores the parts of a QR Code that depend only on the version number,
// and does not depend on the data or error correction level or mask.
final class QrTemplate { final class QrTemplate {
// Use this memoizer to get instances of this class.
public static final Memoizer<Integer,QrTemplate> MEMOIZER public static final Memoizer<Integer,QrTemplate> MEMOIZER
= new Memoizer<>(QrTemplate::new); = new Memoizer<>(QrTemplate::new);
private final int version; private final int version; // In the range [1, 40].
private final int size; private final int size; // Derived from version.
final int[] template; final int[] template; // Length and values depend on version.
final int[][] masks; final int[][] masks; // masks.length == 8, and masks[i].length == template.length.
final int[] dataOutputBitIndexes; final int[] dataOutputBitIndexes; // Length and values depend on version.
// Indicates function modules that are not subjected to masking. Discarded when constructor finishes. // Indicates function modules that are not subjected to masking. Discarded when constructor finishes.
// Otherwise when the constructor is running, isFunction.length == template.length.
private int[] isFunction; private int[] isFunction;
// Creates a QR Code template for the given version number.
private QrTemplate(int ver) { private QrTemplate(int ver) {
if (ver < QrCode.MIN_VERSION || ver > QrCode.MAX_VERSION) if (ver < QrCode.MIN_VERSION || ver > QrCode.MAX_VERSION)
throw new IllegalArgumentException("Version out of range"); throw new IllegalArgumentException("Version out of range");
@ -56,6 +61,7 @@ final class QrTemplate {
} }
// Reads this object's version field, and draws and marks all function modules.
private void drawFunctionPatterns() { private void drawFunctionPatterns() {
// Draw horizontal and vertical timing patterns // Draw horizontal and vertical timing patterns
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
@ -101,7 +107,7 @@ final class QrTemplate {
darkenFunctionModule(size - 1 - i, 8, 0); darkenFunctionModule(size - 1 - i, 8, 0);
for (int i = 8; i < 15; i++) for (int i = 8; i < 15; i++)
darkenFunctionModule(8, size - 15 + i, 0); darkenFunctionModule(8, size - 15 + i, 0);
darkenFunctionModule(8, size - 8, 1); darkenFunctionModule(8, size - 8, 1); // Always black
} }
@ -153,6 +159,7 @@ final class QrTemplate {
} }
// Computes and returns a new array of masks, based on this object's various fields.
private int[][] generateMasks() { private int[][] generateMasks() {
int[][] result = new int[8][template.length]; int[][] result = new int[8][template.length];
for (int mask = 0; mask < result.length; mask++) { for (int mask = 0; mask < result.length; mask++) {
@ -180,6 +187,7 @@ final class QrTemplate {
} }
// Computes and returns an array of bit indexes, based on this object's various fields.
private int[] generateZigzagScan() { private int[] generateZigzagScan() {
int[] result = new int[getNumRawDataModules(version) / 8 * 8]; int[] result = new int[getNumRawDataModules(version) / 8 * 8];
int i = 0; // Bit index into the data int i = 0; // Bit index into the data
@ -203,6 +211,7 @@ final class QrTemplate {
} }
// Returns the value of the bit at the given coordinates in the given grid.
private int getModule(int[] grid, int x, int y) { private int getModule(int[] grid, int x, int y) {
assert 0 <= x && x < size; assert 0 <= x && x < size;
assert 0 <= y && y < size; assert 0 <= y && y < size;

@ -27,29 +27,31 @@ import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
// Computes Reed-Solomon error correction codewords for given data codewords.
final class ReedSolomonGenerator { final class ReedSolomonGenerator {
// Use this memoizer to get instances of this class.
public static final Memoizer<Integer,ReedSolomonGenerator> MEMOIZER public static final Memoizer<Integer,ReedSolomonGenerator> MEMOIZER
= new Memoizer<>(ReedSolomonGenerator::new); = new Memoizer<>(ReedSolomonGenerator::new);
// A table of size 256 * degree, where polynomialMultiply[i][j] = multiply(i, coefficients[j]). // A table of size 256 * degree, where polynomialMultiply[i][j] = multiply(i, coefficients[j]).
// 'coefficients' is the temporary array representing the coefficients of the divisor polynomial, // 'coefficients' is the temporary array computed in the constructor.
// stored from highest to lowest power, excluding the leading term which is always 1.
// For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}.
private byte[][] polynomialMultiply; private byte[][] polynomialMultiply;
// Creates a Reed-Solomon ECC generator polynomial for the given degree.
private ReedSolomonGenerator(int degree) { private ReedSolomonGenerator(int degree) {
if (degree < 1 || degree > 255) if (degree < 1 || degree > 255)
throw new IllegalArgumentException("Degree out of range"); throw new IllegalArgumentException("Degree out of range");
// Start with the monomial x^0 // The divisor polynomial, whose coefficients are stored from highest to lowest power.
// For example, x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}.
byte[] coefficients = new byte[degree]; byte[] coefficients = new byte[degree];
coefficients[degree - 1] = 1; coefficients[degree - 1] = 1; // Start off with the monomial x^0
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
// drop the highest term, and store the rest of the coefficients in order of descending powers. // and drop the highest monomial term which is always 1x^degree.
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
int root = 1; int root = 1;
for (int i = 0; i < degree; i++) { for (int i = 0; i < degree; i++) {
@ -70,15 +72,15 @@ final class ReedSolomonGenerator {
} }
// Returns the error correction codeword for the given data polynomial and this divisor polynomial.
public void getRemainder(byte[] data, int dataOff, int dataLen, byte[] result) { public void getRemainder(byte[] data, int dataOff, int dataLen, byte[] result) {
Objects.requireNonNull(data); Objects.requireNonNull(data);
Objects.requireNonNull(result); Objects.requireNonNull(result);
int degree = polynomialMultiply[0].length; int degree = polynomialMultiply[0].length;
assert result.length == degree; assert result.length == degree;
// Compute the remainder by performing polynomial division
Arrays.fill(result, (byte)0); Arrays.fill(result, (byte)0);
for (int i = dataOff, dataEnd = dataOff + dataLen; i < dataEnd; i++) { for (int i = dataOff, dataEnd = dataOff + dataLen; i < dataEnd; i++) { // Polynomial division
byte[] table = polynomialMultiply[(data[i] ^ result[0]) & 0xFF]; byte[] table = polynomialMultiply[(data[i] ^ result[0]) & 0xFF];
for (int j = 0; j < degree - 1; j++) for (int j = 0; j < degree - 1; j++)
result[j] = (byte)(result[j + 1] ^ table[j]); result[j] = (byte)(result[j + 1] ^ table[j]);

Loading…
Cancel
Save