From b5aaadf75842c1fac49c8acc8a3eb988651f1001 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 14 Jul 2019 16:54:00 +0000 Subject: [PATCH] Demoted ReedSolomonGenerator from a class to a set of functions, and changed some names and comments, in all languages except C. This reduces code verbosity but doesn't change public APIs or visible behavior. The code organization is similar to the finder-like-pattern-detector feature. --- cpp/QrCode.cpp | 107 ++++++------ cpp/QrCode.hpp | 59 ++----- .../main/java/io/nayuki/qrcodegen/QrCode.java | 156 +++++++----------- javascript/qrcodegen.js | 134 +++++++-------- python/qrcodegen.py | 114 ++++++------- rust/src/lib.rs | 131 +++++++-------- typescript/qrcodegen.ts | 141 +++++++--------- 7 files changed, 361 insertions(+), 481 deletions(-) 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.