diff --git a/cpp/QrCode.cpp b/cpp/QrCode.cpp index 9880c54..56a3e3f 100644 --- a/cpp/QrCode.cpp +++ b/cpp/QrCode.cpp @@ -345,11 +345,11 @@ vector QrCode::addEccAndInterleave(const vector &data) const { // Split data into blocks and append ECC to each block vector > blocks; - const ReedSolomonGenerator rs(blockEccLen); + const vector rsDiv = reedSolomonComputeDivisor(blockEccLen); for (int i = 0, k = 0; i < numBlocks; i++) { vector dat(data.cbegin() + k, data.cbegin() + (k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1))); k += dat.size(); - const vector ecc = rs.getRemainder(dat); + const vector ecc = reedSolomonComputeRemainder(dat, rsDiv); if (i < numShortBlocks) dat.push_back(0); dat.insert(dat.end(), ecc.cbegin(), ecc.cend()); @@ -538,6 +538,57 @@ int QrCode::getNumDataCodewords(int ver, Ecc ecl) { } +vector QrCode::reedSolomonComputeDivisor(int degree) { + if (degree < 1 || degree > 255) + throw std::domain_error("Degree out of range"); + // Polynomial coefficients are 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}. + vector result(degree); + result.at(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}), + // 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). + uint8_t root = 1; + for (int i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (size_t j = 0; j < result.size(); j++) { + result.at(j) = reedSolomonMultiply(result.at(j), root); + if (j + 1 < result.size()) + result.at(j) ^= result.at(j + 1); + } + root = reedSolomonMultiply(root, 0x02); + } + return result; +} + + +vector QrCode::reedSolomonComputeRemainder(const vector &data, const vector &divisor) { + vector result(divisor.size()); + for (uint8_t b : data) { // Polynomial division + uint8_t factor = b ^ result.at(0); + result.erase(result.begin()); + result.push_back(0); + for (size_t j = 0; j < result.size(); j++) + result.at(j) ^= reedSolomonMultiply(divisor.at(j), factor); + } + return result; +} + + +uint8_t QrCode::reedSolomonMultiply(uint8_t x, uint8_t y) { + // Russian peasant multiplication + int z = 0; + for (int i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + if (z >> 8 != 0) + throw std::logic_error("Assertion error"); + return static_cast(z); +} + + int QrCode::finderPenaltyCountPatterns(const std::array &runHistory) const { int n = runHistory.at(1); if (n > size * 3) @@ -597,58 +648,6 @@ const int8_t QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = { }; -QrCode::ReedSolomonGenerator::ReedSolomonGenerator(int degree) : - coefficients() { - if (degree < 1 || degree > 255) - throw std::domain_error("Degree out of range"); - - // Start with the monomial x^0 - coefficients.resize(degree); - coefficients.at(degree - 1) = 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. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - uint8_t root = 1; - for (int i = 0; i < degree; i++) { - // Multiply the current product by (x - r^i) - for (size_t j = 0; j < coefficients.size(); j++) { - coefficients.at(j) = multiply(coefficients.at(j), root); - if (j + 1 < coefficients.size()) - coefficients.at(j) ^= coefficients.at(j + 1); - } - root = multiply(root, 0x02); - } -} - - -vector QrCode::ReedSolomonGenerator::getRemainder(const vector &data) const { - // Compute the remainder by performing polynomial division - vector result(coefficients.size()); - for (uint8_t b : data) { - uint8_t factor = b ^ result.at(0); - result.erase(result.begin()); - result.push_back(0); - for (size_t j = 0; j < result.size(); j++) - result.at(j) ^= multiply(coefficients.at(j), factor); - } - return result; -} - - -uint8_t QrCode::ReedSolomonGenerator::multiply(uint8_t x, uint8_t y) { - // Russian peasant multiplication - int z = 0; - for (int i = 7; i >= 0; i--) { - z = (z << 1) ^ ((z >> 7) * 0x11D); - z ^= ((y >> i) & 1) * x; - } - if (z >> 8 != 0) - throw std::logic_error("Assertion error"); - return static_cast(z); -} - - data_too_long::data_too_long(const std::string &msg) : std::length_error(msg) {} diff --git a/cpp/QrCode.hpp b/cpp/QrCode.hpp index 6196355..8ad519f 100644 --- a/cpp/QrCode.hpp +++ b/cpp/QrCode.hpp @@ -274,6 +274,20 @@ class QrCode final { private: static int getNumDataCodewords(int ver, Ecc ecl); + // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be + // implemented as a lookup table over all possible parameter values, instead of as an algorithm. + private: static std::vector reedSolomonComputeDivisor(int degree); + + + // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. + private: static std::vector reedSolomonComputeRemainder(const std::vector &data, const std::vector &divisor); + + + // Returns the product of the two given field elements modulo GF(2^8/0x11D). + // All inputs are valid. This could be implemented as a 256*256 lookup table. + private: static std::uint8_t reedSolomonMultiply(std::uint8_t x, std::uint8_t y); + + // Can only be called immediately after a white run is added, and // returns either 0, 1, or 2. A helper function for getPenaltyScore(). private: int finderPenaltyCountPatterns(const std::array &runHistory) const; @@ -310,51 +324,6 @@ class QrCode final { private: static const std::int8_t ECC_CODEWORDS_PER_BLOCK[4][41]; private: static const std::int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41]; - - - /*---- Private helper class ----*/ - - /* - * Computes the Reed-Solomon error correction codewords for a sequence of data codewords - * at a given degree. Objects are immutable, and the state only depends on the degree. - * This class exists because each data block in a QR Code shares the same the divisor polynomial. - */ - private: class ReedSolomonGenerator final { - - /*-- Immutable field --*/ - - // Coefficients of the divisor polynomial, 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: std::vector coefficients; - - - /*-- Constructor --*/ - - /* - * Creates a Reed-Solomon ECC generator for the given degree. This could be implemented - * as a lookup table over all possible parameter values, instead of as an algorithm. - */ - public: explicit ReedSolomonGenerator(int degree); - - - /*-- Method --*/ - - /* - * Computes and returns the Reed-Solomon error correction codewords for the given - * sequence of data codewords. The returned object is always a new byte array. - * This method does not alter this object's state (because it is immutable). - */ - public: std::vector getRemainder(const std::vector &data) const; - - - /*-- Static function --*/ - - // Returns the product of the two given field elements modulo GF(2^8/0x11D). - // All inputs are valid. This could be implemented as a 256*256 lookup table. - private: static std::uint8_t multiply(std::uint8_t x, std::uint8_t y); - - }; - }; diff --git a/java/src/main/java/io/nayuki/qrcodegen/QrCode.java b/java/src/main/java/io/nayuki/qrcodegen/QrCode.java index 21b347a..b82ee1f 100644 --- a/java/src/main/java/io/nayuki/qrcodegen/QrCode.java +++ b/java/src/main/java/io/nayuki/qrcodegen/QrCode.java @@ -483,12 +483,12 @@ public final class QrCode { // Split data into blocks and append ECC to each block byte[][] blocks = new byte[numBlocks][]; - ReedSolomonGenerator rs = new ReedSolomonGenerator(blockEccLen); + byte[] rsDiv = reedSolomonComputeDivisor(blockEccLen); for (int i = 0, k = 0; i < numBlocks; i++) { byte[] dat = Arrays.copyOfRange(data, k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1)); k += dat.length; byte[] block = Arrays.copyOf(dat, shortBlockLen + 1); - byte[] ecc = rs.getRemainder(dat); + byte[] ecc = reedSolomonComputeRemainder(dat, rsDiv); System.arraycopy(ecc, 0, block, block.length - blockEccLen, ecc.length); blocks[i] = block; } @@ -722,6 +722,64 @@ public final class QrCode { } + // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be + // implemented as a lookup table over all possible parameter values, instead of as an algorithm. + private static byte[] reedSolomonComputeDivisor(int degree) { + if (degree < 1 || degree > 255) + throw new IllegalArgumentException("Degree out of range"); + // Polynomial coefficients are 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}. + byte[] result = new byte[degree]; + result[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}), + // 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). + int root = 1; + for (int i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (int j = 0; j < result.length; j++) { + result[j] = (byte)reedSolomonMultiply(result[j] & 0xFF, root); + if (j + 1 < result.length) + result[j] ^= result[j + 1]; + } + root = reedSolomonMultiply(root, 0x02); + } + return result; + } + + + // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. + private static byte[] reedSolomonComputeRemainder(byte[] data, byte[] divisor) { + Objects.requireNonNull(data); + Objects.requireNonNull(divisor); + byte[] result = new byte[divisor.length]; + for (byte b : data) { // Polynomial division + int factor = (b ^ result[0]) & 0xFF; + System.arraycopy(result, 1, result, 0, result.length - 1); + result[result.length - 1] = 0; + for (int i = 0; i < result.length; i++) + result[i] ^= reedSolomonMultiply(divisor[i] & 0xFF, factor); + } + return result; + } + + + // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result + // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. + private static int reedSolomonMultiply(int x, int y) { + assert x >>> 8 == 0 && y >>> 8 == 0; + // Russian peasant multiplication + int z = 0; + for (int i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >>> 7) * 0x11D); + z ^= ((y >>> i) & 1) * x; + } + assert z >>> 8 == 0; + return z; + } + + // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any // QR Code of the given version number and error correction level, with remainder bits discarded. // This stateless pure function could be implemented as a (40*4)-cell lookup table. @@ -826,98 +884,4 @@ public final class QrCode { } } - - - /*---- Private helper class ----*/ - - /** - * Computes the Reed-Solomon error correction codewords for a sequence of data codewords - * at a given degree. Objects are immutable, and the state only depends on the degree. - * This class exists because each data block in a QR Code shares the same the divisor polynomial. - */ - private static final class ReedSolomonGenerator { - - /*-- Field --*/ - - // Coefficients of the divisor polynomial, 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 final byte[] coefficients; - - - /*-- Constructor --*/ - - /** - * Constructs a Reed-Solomon ECC generator for the specified degree. This could be implemented - * as a lookup table over all possible parameter values, instead of as an algorithm. - * @param degree the divisor polynomial degree, which must be between 1 and 255 (inclusive) - * @throws IllegalArgumentException if degree < 1 or degree > 255 - */ - public ReedSolomonGenerator(int degree) { - if (degree < 1 || degree > 255) - throw new IllegalArgumentException("Degree out of range"); - - // Start with the monomial x^0 - coefficients = new byte[degree]; - coefficients[degree - 1] = 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. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - int root = 1; - for (int i = 0; i < degree; i++) { - // Multiply the current product by (x - r^i) - for (int j = 0; j < coefficients.length; j++) { - coefficients[j] = (byte)multiply(coefficients[j] & 0xFF, root); - if (j + 1 < coefficients.length) - coefficients[j] ^= coefficients[j + 1]; - } - root = multiply(root, 0x02); - } - } - - - /*-- Method --*/ - - /** - * Computes and returns the Reed-Solomon error correction codewords for the specified - * sequence of data codewords. The returned object is always a new byte array. - * This method does not alter this object's state (because it is immutable). - * @param data the sequence of data codewords - * @return the Reed-Solomon error correction codewords - * @throws NullPointerException if the data is {@code null} - */ - public byte[] getRemainder(byte[] data) { - Objects.requireNonNull(data); - - // Compute the remainder by performing polynomial division - byte[] result = new byte[coefficients.length]; - for (byte b : data) { - int factor = (b ^ result[0]) & 0xFF; - System.arraycopy(result, 1, result, 0, result.length - 1); - result[result.length - 1] = 0; - for (int i = 0; i < result.length; i++) - result[i] ^= multiply(coefficients[i] & 0xFF, factor); - } - return result; - } - - - /*-- Static function --*/ - - // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result - // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. - private static int multiply(int x, int y) { - assert x >>> 8 == 0 && y >>> 8 == 0; - // Russian peasant multiplication - int z = 0; - for (int i = 7; i >= 0; i--) { - z = (z << 1) ^ ((z >>> 7) * 0x11D); - z ^= ((y >>> i) & 1) * x; - } - assert z >>> 8 == 0; - return z; - } - - } - } diff --git a/javascript/qrcodegen.js b/javascript/qrcodegen.js index 40aff87..503e5ec 100644 --- a/javascript/qrcodegen.js +++ b/javascript/qrcodegen.js @@ -339,11 +339,11 @@ var qrcodegen = new function() { // Split data into blocks and append ECC to each block var blocks = []; - var rs = new ReedSolomonGenerator(blockEccLen); + var rsDiv = QrCode.reedSolomonComputeDivisor(blockEccLen); for (var i = 0, k = 0; i < numBlocks; i++) { var dat = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1)); k += dat.length; - var ecc = rs.getRemainder(dat); + var ecc = QrCode.reedSolomonComputeRemainder(dat, rsDiv); if (i < numShortBlocks) dat.push(0); blocks.push(dat.concat(ecc)); @@ -687,6 +687,66 @@ var qrcodegen = new function() { }; + // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be + // implemented as a lookup table over all possible parameter values, instead of as an algorithm. + QrCode.reedSolomonComputeDivisor = function(degree) { + if (degree < 1 || degree > 255) + throw "Degree out of range"; + // Polynomial coefficients are 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}. + var result = []; + for (var i = 0; i < degree - 1; i++) + result.push(0); + result.push(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}), + // 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). + var root = 1; + for (var i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (var j = 0; j < result.length; j++) { + result[j] = QrCode.reedSolomonMultiply(result[j], root); + if (j + 1 < result.length) + result[j] ^= result[j + 1]; + } + root = QrCode.reedSolomonMultiply(root, 0x02); + } + return result; + }; + + + // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. + QrCode.reedSolomonComputeRemainder = function(data, divisor) { + var result = divisor.map(function() { return 0; }); + data.forEach(function(b) { // Polynomial division + var factor = b ^ result.shift(); + result.push(0); + divisor.forEach(function(coef, i) { + result[i] ^= QrCode.reedSolomonMultiply(coef, factor); + }); + }); + return result; + }; + + + // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result + // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. + QrCode.reedSolomonMultiply = function(x, y) { + if (x >>> 8 != 0 || y >>> 8 != 0) + throw "Byte out of range"; + // Russian peasant multiplication + var z = 0; + for (var i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >>> 7) * 0x11D); + z ^= ((y >>> i) & 1) * x; + } + if (z >>> 8 != 0) + throw "Assertion error"; + return z; + }; + + // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). QrCode.finderPenaltyAddHistory = function(currentRunLength, runHistory) { runHistory.pop(); @@ -972,76 +1032,6 @@ var qrcodegen = new function() { } - - /* - * A private helper class that computes the Reed-Solomon error correction codewords for a sequence of - * data codewords at a given degree. Objects are immutable, and the state only depends on the degree. - * This class exists because each data block in a QR Code shares the same the divisor polynomial. - * This constructor creates a Reed-Solomon ECC generator for the given degree. This could be implemented - * as a lookup table over all possible parameter values, instead of as an algorithm. - */ - function ReedSolomonGenerator(degree) { - if (degree < 1 || degree > 255) - throw "Degree out of range"; - - // Coefficients of the divisor polynomial, 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}. - var coefficients = []; - - // Start with the monomial x^0 - for (var i = 0; i < degree - 1; i++) - coefficients.push(0); - coefficients.push(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. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - var root = 1; - for (var i = 0; i < degree; i++) { - // Multiply the current product by (x - r^i) - for (var j = 0; j < coefficients.length; j++) { - coefficients[j] = ReedSolomonGenerator.multiply(coefficients[j], root); - if (j + 1 < coefficients.length) - coefficients[j] ^= coefficients[j + 1]; - } - root = ReedSolomonGenerator.multiply(root, 0x02); - } - - // Computes and returns the Reed-Solomon error correction codewords for the given - // sequence of data codewords. The returned object is always a new byte array. - // This method does not alter this object's state (because it is immutable). - this.getRemainder = function(data) { - // Compute the remainder by performing polynomial division - var result = coefficients.map(function() { return 0; }); - data.forEach(function(b) { - var factor = b ^ result.shift(); - result.push(0); - coefficients.forEach(function(coef, i) { - result[i] ^= ReedSolomonGenerator.multiply(coef, factor); - }); - }); - return result; - }; - } - - // This static function returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and - // result are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. - ReedSolomonGenerator.multiply = function(x, y) { - if (x >>> 8 != 0 || y >>> 8 != 0) - throw "Byte out of range"; - // Russian peasant multiplication - var z = 0; - for (var i = 7; i >= 0; i--) { - z = (z << 1) ^ ((z >>> 7) * 0x11D); - z ^= ((y >>> i) & 1) * x; - } - if (z >>> 8 != 0) - throw "Assertion error"; - return z; - }; - - - /* * A private helper class that represents an appendable sequence of bits (0s and 1s). * Mainly used by QrSegment. This constructor creates an empty bit buffer (length 0). diff --git a/python/qrcodegen.py b/python/qrcodegen.py index 3dd8eca..e1e8fff 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -396,12 +396,12 @@ class QrCode(object): # Split data into blocks and append ECC to each block blocks = [] - rs = _ReedSolomonGenerator(blockecclen) + rsdiv = QrCode._reed_solomon_compute_divisor(blockecclen) k = 0 for i in range(numblocks): dat = data[k : k + shortblocklen - blockecclen + (0 if i < numshortblocks else 1)] k += len(dat) - ecc = rs.get_remainder(dat) + ecc = QrCode._reed_solomon_compute_remainder(dat, rsdiv) if i < numshortblocks: dat.append(0) blocks.append(dat + ecc) @@ -563,6 +563,57 @@ class QrCode(object): * QrCode._NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver] + @staticmethod + def _reed_solomon_compute_divisor(degree): + """Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be + implemented as a lookup table over all possible parameter values, instead of as an algorithm.""" + if degree < 1 or degree > 255: + raise ValueError("Degree out of range") + # Polynomial coefficients are 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}. + result = [0] * (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}), + # 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). + root = 1 + for _ in range(degree): # Unused variable i + # Multiply the current product by (x - r^i) + for j in range(degree): + result[j] = QrCode._reed_solomon_multiply(result[j], root) + if j + 1 < degree: + result[j] ^= result[j + 1] + root = QrCode._reed_solomon_multiply(root, 0x02) + return result + + + @staticmethod + def _reed_solomon_compute_remainder(data, divisor): + """Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials.""" + result = [0] * len(divisor) + for b in data: # Polynomial division + factor = b ^ result.pop(0) + result.append(0) + for (i, coef) in enumerate(divisor): + result[i] ^= QrCode._reed_solomon_multiply(coef, factor) + return result + + + @staticmethod + def _reed_solomon_multiply(x, y): + """Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result + are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8.""" + if x >> 8 != 0 or y >> 8 != 0: + raise ValueError("Byte out of range") + # Russian peasant multiplication + z = 0 + for i in reversed(range(8)): + z = (z << 1) ^ ((z >> 7) * 0x11D) + z ^= ((y >> i) & 1) * x + assert z >> 8 == 0 + return z + + # Can only be called immediately after a white run is added, and # returns either 0, 1, or 2. A helper function for _get_penalty_score(). def _finder_penalty_count_patterns(self, runhistory): @@ -841,64 +892,7 @@ class QrSegment(object): -# ---- Private helper classes ---- - -class _ReedSolomonGenerator(object): - """Computes the Reed-Solomon error correction codewords for a sequence of data codewords - at a given degree. Objects are immutable, and the state only depends on the degree. - This class exists because each data block in a QR Code shares the same the divisor polynomial.""" - - def __init__(self, degree): - """Creates a Reed-Solomon ECC generator for the given degree. This could be implemented - as a lookup table over all possible parameter values, instead of as an algorithm.""" - if degree < 1 or degree > 255: - raise ValueError("Degree out of range") - - # Start with the monomial x^0 - self.coefficients = [0] * (degree - 1) + [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. - # Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - root = 1 - for _ in range(degree): # Unused variable i - # Multiply the current product by (x - r^i) - for j in range(degree): - self.coefficients[j] = _ReedSolomonGenerator._multiply(self.coefficients[j], root) - if j + 1 < degree: - self.coefficients[j] ^= self.coefficients[j + 1] - root = _ReedSolomonGenerator._multiply(root, 0x02) - - - def get_remainder(self, data): - """Computes and returns the Reed-Solomon error correction codewords for the given - sequence of data codewords. The returned object is always a new byte list. - This method does not alter this object's state (because it is immutable).""" - # Compute the remainder by performing polynomial division - result = [0] * len(self.coefficients) - for b in data: - factor = b ^ result.pop(0) - result.append(0) - for (i, coef) in enumerate(self.coefficients): - result[i] ^= _ReedSolomonGenerator._multiply(coef, factor) - return result - - - @staticmethod - def _multiply(x, y): - """Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result - are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8.""" - if x >> 8 != 0 or y >> 8 != 0: - raise ValueError("Byte out of range") - # Russian peasant multiplication - z = 0 - for i in reversed(range(8)): - z = (z << 1) ^ ((z >> 7) * 0x11D) - z ^= ((y >> i) & 1) * x - assert z >> 8 == 0 - return z - - +# ---- Private helper class ---- class _BitBuffer(list): """An appendable sequence of bits (0s and 1s). Mainly used by QrSegment.""" diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 28b5514..d71a979 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -550,12 +550,12 @@ impl QrCode { // Split data into blocks and append ECC to each block let mut blocks = Vec::>::with_capacity(numblocks); - let rs = ReedSolomonGenerator::new(blockecclen); + let rsdiv: Vec = QrCode::reed_solomon_compute_divisor(blockecclen); let mut k: usize = 0; for i in 0 .. numblocks { let mut dat = data[k .. k + shortblocklen - blockecclen + ((i >= numshortblocks) as usize)].to_vec(); k += dat.len(); - let ecc: Vec = rs.get_remainder(&dat); + let ecc: Vec = QrCode::reed_solomon_compute_remainder(&dat, &rsdiv); if i < numshortblocks { dat.push(0); } @@ -773,6 +773,60 @@ impl QrCode { } + // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be + // implemented as a lookup table over all possible parameter values, instead of as an algorithm. + fn reed_solomon_compute_divisor(degree: usize) -> Vec { + assert!(1 <= degree && degree <= 255, "Degree out of range"); + // Polynomial coefficients are 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}. + let mut result = vec![0u8; degree - 1]; + result.push(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}), + // 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). + let mut root: u8 = 1; + for _ in 0 .. degree { // Unused variable i + // Multiply the current product by (x - r^i) + for j in 0 .. degree { + result[j] = QrCode::reed_solomon_multiply(result[j], root); + if j + 1 < result.len() { + result[j] ^= result[j + 1]; + } + } + root = QrCode::reed_solomon_multiply(root, 0x02); + } + result + } + + + // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. + fn reed_solomon_compute_remainder(data: &[u8], divisor: &[u8]) -> Vec { + let mut result = vec![0u8; divisor.len()]; + for b in data { // Polynomial division + let factor: u8 = b ^ result.remove(0); + result.push(0); + for (x, y) in result.iter_mut().zip(divisor.iter()) { + *x ^= QrCode::reed_solomon_multiply(*y, factor); + } + } + result + } + + + // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result + // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. + fn reed_solomon_multiply(x: u8, y: u8) -> u8 { + // Russian peasant multiplication + let mut z: u8 = 0; + for i in (0 .. 8).rev() { + z = (z << 1) ^ ((z >> 7) * 0x1D); + z ^= ((y >> i) & 1) * x; + } + z + } + + // Can only be called immediately after a white run is added, and // returns either 0, 1, or 2. A helper function for get_penalty_score(). fn finder_penalty_count_patterns(&self, runhistory: &[i32;7]) -> i32 { @@ -888,79 +942,6 @@ impl QrCodeEcc { -/*---- ReedSolomonGenerator functionality ----*/ - -// Computes the Reed-Solomon error correction codewords for a sequence of data codewords -// at a given degree. Objects are immutable, and the state only depends on the degree. -// This struct and impl exist because each data block in a QR Code shares the same the divisor polynomial. -struct ReedSolomonGenerator { - - // Coefficients of the divisor polynomial, 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}. - coefficients: Vec, - -} - - -impl ReedSolomonGenerator { - - // Creates a Reed-Solomon ECC generator for the given degree. This could be implemented - // as a lookup table over all possible parameter values, instead of as an algorithm. - fn new(degree: usize) -> Self { - assert!(1 <= degree && degree <= 255, "Degree out of range"); - // Start with the monomial x^0 - let mut coefs = vec![0u8; degree - 1]; - coefs.push(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. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - let mut root: u8 = 1; - for _ in 0 .. degree { // Unused variable i - // Multiply the current product by (x - r^i) - for j in 0 .. degree { - coefs[j] = ReedSolomonGenerator::multiply(coefs[j], root); - if j + 1 < coefs.len() { - coefs[j] ^= coefs[j + 1]; - } - } - root = ReedSolomonGenerator::multiply(root, 0x02); - } - Self { coefficients: coefs } - } - - - // Computes and returns the Reed-Solomon error correction codewords for the given sequence of data codewords. - fn get_remainder(&self, data: &[u8]) -> Vec { - // Compute the remainder by performing polynomial division - let mut result = vec![0u8; self.coefficients.len()]; - for b in data { - let factor: u8 = b ^ result.remove(0); - result.push(0); - for (x, y) in result.iter_mut().zip(self.coefficients.iter()) { - *x ^= ReedSolomonGenerator::multiply(*y, factor); - } - } - result - } - - - // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result - // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. - fn multiply(x: u8, y: u8) -> u8 { - // Russian peasant multiplication - let mut z: u8 = 0; - for i in (0 .. 8).rev() { - z = (z << 1) ^ ((z >> 7) * 0x1D); - z ^= ((y >> i) & 1) * x; - } - z - } - -} - - - /*---- QrSegment functionality ----*/ /// A segment of character/binary/control data in a QR Code symbol. diff --git a/typescript/qrcodegen.ts b/typescript/qrcodegen.ts index 3a3b03a..f43e85e 100644 --- a/typescript/qrcodegen.ts +++ b/typescript/qrcodegen.ts @@ -422,11 +422,11 @@ namespace qrcodegen { // Split data into blocks and append ECC to each block let blocks: Array> = []; - const rs = new ReedSolomonGenerator(blockEccLen); + const rsDiv: Array = QrCode.reedSolomonComputeDivisor(blockEccLen); for (let i = 0, k = 0; i < numBlocks; i++) { let dat: Array = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1)); k += dat.length; - const ecc: Array = rs.getRemainder(dat); + const ecc: Array = QrCode.reedSolomonComputeRemainder(dat, rsDiv); if (i < numShortBlocks) dat.push(0); blocks.push(dat.concat(ecc)); @@ -633,6 +633,65 @@ namespace qrcodegen { } + // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be + // implemented as a lookup table over all possible parameter values, instead of as an algorithm. + private static reedSolomonComputeDivisor(degree: int): Array { + if (degree < 1 || degree > 255) + throw "Degree out of range"; + // Polynomial coefficients are 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}. + let result: Array = []; + for (let i = 0; i < degree - 1; i++) + result.push(0); + result.push(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}), + // 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). + let root = 1; + for (let i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (let j = 0; j < result.length; j++) { + result[j] = QrCode.reedSolomonMultiply(result[j], root); + if (j + 1 < result.length) + result[j] ^= result[j + 1]; + } + root = QrCode.reedSolomonMultiply(root, 0x02); + } + return result; + } + + + // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. + private static reedSolomonComputeRemainder(data: Array, divisor: Array): Array { + let result: Array = divisor.map(_ => 0); + for (const b of data) { // Polynomial division + const factor: byte = b ^ (result.shift() as byte); + result.push(0); + divisor.forEach((coef, i) => + result[i] ^= QrCode.reedSolomonMultiply(coef, factor)); + } + return result; + } + + + // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result + // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. + private static reedSolomonMultiply(x: byte, y: byte): byte { + if (x >>> 8 != 0 || y >>> 8 != 0) + throw "Byte out of range"; + // Russian peasant multiplication + let z: int = 0; + for (let i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >>> 7) * 0x11D); + z ^= ((y >>> i) & 1) * x; + } + if (z >>> 8 != 0) + throw "Assertion error"; + return z as byte; + } + + // Can only be called immediately after a white run is added, and // returns either 0, 1, or 2. A helper function for getPenaltyScore(). private finderPenaltyCountPatterns(runHistory: Array): int { @@ -883,83 +942,7 @@ namespace qrcodegen { - /*---- Private helper classes ----*/ - - /* - * Computes the Reed-Solomon error correction codewords for a sequence of data codewords - * at a given degree. Objects are immutable, and the state only depends on the degree. - * This class exists because each data block in a QR Code shares the same the divisor polynomial. - */ - class ReedSolomonGenerator { - - // Coefficients of the divisor polynomial, 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 readonly coefficients: Array = []; - - - // Creates a Reed-Solomon ECC generator for the given degree. This could be implemented - // as a lookup table over all possible parameter values, instead of as an algorithm. - public constructor(degree: int) { - if (degree < 1 || degree > 255) - throw "Degree out of range"; - let coefs = this.coefficients; - - // Start with the monomial x^0 - for (let i = 0; i < degree - 1; i++) - coefs.push(0); - coefs.push(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. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - let root = 1; - for (let i = 0; i < degree; i++) { - // Multiply the current product by (x - r^i) - for (let j = 0; j < coefs.length; j++) { - coefs[j] = ReedSolomonGenerator.multiply(coefs[j], root); - if (j + 1 < coefs.length) - coefs[j] ^= coefs[j + 1]; - } - root = ReedSolomonGenerator.multiply(root, 0x02); - } - } - - - // Computes and returns the Reed-Solomon error correction codewords for the given - // sequence of data codewords. The returned object is always a new byte array. - // This method does not alter this object's state (because it is immutable). - public getRemainder(data: Array): Array { - // Compute the remainder by performing polynomial division - let result: Array = this.coefficients.map(_ => 0); - for (const b of data) { - const factor: byte = b ^ (result.shift() as byte); - result.push(0); - this.coefficients.forEach((coef, i) => - result[i] ^= ReedSolomonGenerator.multiply(coef, factor)); - } - return result; - } - - - // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result - // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. - private static multiply(x: byte, y: byte): byte { - if (x >>> 8 != 0 || y >>> 8 != 0) - throw "Byte out of range"; - // Russian peasant multiplication - let z: int = 0; - for (let i = 7; i >= 0; i--) { - z = (z << 1) ^ ((z >>> 7) * 0x11D); - z ^= ((y >>> i) & 1) * x; - } - if (z >>> 8 != 0) - throw "Assertion error"; - return z as byte; - } - - } - - + /*---- Private helper class ----*/ /* * An appendable sequence of bits (0s and 1s). Mainly used by QrSegment.