From 6be9684df4cf1561e062024b1de8123da6e8b7a3 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Thu, 23 Nov 2017 03:06:55 +0000 Subject: [PATCH 01/85] Started this "Fast QR Code generator library" project, added Reed-Solomon ECC generator. --- .../fastqrcodegen/ReedSolomonGenerator.java | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java new file mode 100644 index 0000000..8be42e7 --- /dev/null +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -0,0 +1,141 @@ +package io.nayuki.fastqrcodegen; + +import java.lang.ref.SoftReference; +import java.util.Arrays; +import java.util.Objects; + + +final class ReedSolomonGenerator { + + /*---- Factory members ----*/ + + public static ReedSolomonGenerator getInstance(int degree) { + if (degree < 1 || degree > MAX_DEGREE) + throw new IllegalArgumentException("Degree out of range"); + + while (true) { + synchronized(cache) { + SoftReference ref = cache[degree]; + if (ref != null) { + ReedSolomonGenerator result = ref.get(); + if (result != null) + return result; + cache[degree] = null; + } + + if (!isPending[degree]) { + isPending[degree] = true; + break; + } + + try { + cache.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + ReedSolomonGenerator rs = new ReedSolomonGenerator(degree); + synchronized(cache) { + cache[degree] = new SoftReference<>(rs); + isPending[degree] = false; + cache.notifyAll(); + } + return rs; + } + + + private static final int MAX_DEGREE = 30; + + @SuppressWarnings("unchecked") + private static final SoftReference[] cache = new SoftReference[MAX_DEGREE + 1]; + + private static final boolean[] isPending = new boolean[MAX_DEGREE + 1]; + + + + /*---- Instance members ----*/ + + private byte[][] multiplies; + + + private ReedSolomonGenerator(int degree) { + if (degree < 1 || degree > 255) + throw new IllegalArgumentException("Degree out of range"); + + // Start with the monomial x^0 + byte[] 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); + } + + multiplies = new byte[degree][]; + for (int i = 0; i < multiplies.length; i++) + multiplies[i] = MULTIPLICATION_TABLE[coefficients[i] & 0xFF]; + } + + + public void getRemainder(byte[] data, byte[] result) { + Objects.requireNonNull(data); + Objects.requireNonNull(result); + if (result.length != multiplies.length) + throw new IllegalArgumentException("Array length mismatch"); + + // Compute the remainder by performing polynomial division + Arrays.fill(result, (byte)0); + 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] ^= multiplies[i][factor]; + } + } + + + + /*---- Constant members ----*/ + + // 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) { + if (x >>> 8 != 0 || y >>> 8 != 0) + throw new IllegalArgumentException("Byte out of range"); + // 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 new AssertionError(); + return z; + } + + + private static final byte[][] MULTIPLICATION_TABLE = new byte[256][256]; + + static { + for (int i = 0; i < MULTIPLICATION_TABLE.length; i++) { + for (int j = 0; j <= i; j++) { + byte k = (byte)multiply(i, j); + MULTIPLICATION_TABLE[i][j] = k; + MULTIPLICATION_TABLE[j][i] = k; + } + } + } + +} From f8a3eb7320fd1cc6c618a79d22743fbd86f69acc Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Thu, 23 Nov 2017 03:45:16 +0000 Subject: [PATCH 02/85] Implemented bit buffer class. --- src/io/nayuki/fastqrcodegen/BitBuffer.java | 56 ++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/io/nayuki/fastqrcodegen/BitBuffer.java diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java new file mode 100644 index 0000000..c612029 --- /dev/null +++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java @@ -0,0 +1,56 @@ +package io.nayuki.fastqrcodegen; + +import java.util.Arrays; + + +final class BitBuffer { + + /*---- Fields ----*/ + + private int[] data; + + private int bitLength; + + + + /*---- Constructors ----*/ + + public BitBuffer() { + data = new int[64]; + bitLength = 0; + } + + + + /*---- Methods ----*/ + + public int getBit(int index) { + if (index < 0 || index >= bitLength) + throw new IndexOutOfBoundsException(); + return (data[index >>> 5] >>> ~index) & 1; + } + + + public void appendBits(int val, int len) { + if (len < 0 || len > 31 || val >>> len != 0) + throw new IllegalArgumentException("Value out of range"); + + if (bitLength + len + 1 > data.length << 5) + data = Arrays.copyOf(data, data.length * 2); + assert bitLength + len <= data.length << 5; + + int remain = 32 - (bitLength & 0x1F); + assert 1 <= remain && remain <= 32; + if (remain < len) { + data[bitLength >>> 5] |= val >>> (len - remain); + bitLength += remain; + assert (bitLength & 0x1F) == 0; + len -= remain; + val &= (1 << len) - 1; + remain = 32; + } + data[bitLength >>> 5] |= val << (remain - len); + bitLength += len; + } + +} From 477f4eadd29201c54637bcfae52812e5646df985 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Thu, 23 Nov 2017 03:48:40 +0000 Subject: [PATCH 03/85] Added MIT open-source license to header of all files. --- src/io/nayuki/fastqrcodegen/BitBuffer.java | 23 +++++++++++++++++++ .../fastqrcodegen/ReedSolomonGenerator.java | 23 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java index c612029..ec47819 100644 --- a/src/io/nayuki/fastqrcodegen/BitBuffer.java +++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java @@ -1,3 +1,26 @@ +/* + * Fast QR Code generator library + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + package io.nayuki.fastqrcodegen; import java.util.Arrays; diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java index 8be42e7..2a35fce 100644 --- a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -1,3 +1,26 @@ +/* + * Fast QR Code generator library + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + package io.nayuki.fastqrcodegen; import java.lang.ref.SoftReference; From d842b7ca57efbc179de6110483206788304f4a8d Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Thu, 23 Nov 2017 04:02:58 +0000 Subject: [PATCH 04/85] Implement QR segment class, exposed bit buffer fields to the package. --- src/io/nayuki/fastqrcodegen/BitBuffer.java | 4 +- src/io/nayuki/fastqrcodegen/QrSegment.java | 227 +++++++++++++++++++++ 2 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 src/io/nayuki/fastqrcodegen/QrSegment.java diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java index ec47819..a73158b 100644 --- a/src/io/nayuki/fastqrcodegen/BitBuffer.java +++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java @@ -30,9 +30,9 @@ final class BitBuffer { /*---- Fields ----*/ - private int[] data; + int[] data; - private int bitLength; + int bitLength; diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java new file mode 100644 index 0000000..44a9689 --- /dev/null +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -0,0 +1,227 @@ +/* + * Fast QR Code generator library + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.fastqrcodegen; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + + +public final class QrSegment { + + /*---- Static factory functions ----*/ + + public static QrSegment makeBytes(byte[] data) { + Objects.requireNonNull(data); + int[] bits = new int[(data.length + 3) / 4]; + for (int i = 0; i < data.length; i++) + bits[i >>> 2] |= (data[i] & 0xFF) << (~i << 3); + return new QrSegment(Mode.BYTE, data.length, bits, data.length * 8); + } + + + public static QrSegment makeNumeric(String digits) { + Objects.requireNonNull(digits); + BitBuffer bb = new BitBuffer(); + int accumData = 0; + int accumCount = 0; + for (int i = 0; i < digits.length(); i++) { + char c = digits.charAt(i); + if (c < '0' || c > '9') + throw new IllegalArgumentException("String contains non-numeric characters"); + accumData = accumData * 10 + (c - '0'); + accumCount++; + if (accumCount == 3) { + bb.appendBits(accumData, 10); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 or 2 digits remaining + bb.appendBits(accumData, accumCount * 3 + 1); + return new QrSegment(Mode.NUMERIC, digits.length(), bb.data, bb.bitLength); + } + + + public static QrSegment makeAlphanumeric(String text) { + Objects.requireNonNull(text); + BitBuffer bb = new BitBuffer(); + int accumData = 0; + int accumCount = 0; + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c >= ALPHANUMERIC_MAP.length || ALPHANUMERIC_MAP[c] == -1) + throw new IllegalArgumentException("String contains unencodable characters in alphanumeric mode"); + accumData = accumData * 10 + ALPHANUMERIC_MAP[c]; + accumCount++; + if (accumCount == 2) { + bb.appendBits(accumData, 11); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 character remaining + bb.appendBits(accumData, 6); + return new QrSegment(Mode.ALPHANUMERIC, text.length(), bb.data, bb.bitLength); + } + + + public static List makeSegments(String text) { + Objects.requireNonNull(text); + + // Select the most efficient segment encoding automatically + List result = new ArrayList<>(); + if (text.equals("")); // Leave result empty + else if (isNumeric(text)) + result.add(makeNumeric(text)); + else if (isAlphanumeric(text)) + result.add(makeAlphanumeric(text)); + else + result.add(makeBytes(text.getBytes(StandardCharsets.UTF_8))); + return result; + } + + + public static QrSegment makeEci(int assignVal) { + BitBuffer bb = new BitBuffer(); + if (0 <= assignVal && assignVal < (1 << 7)) + bb.appendBits(assignVal, 8); + else if ((1 << 7) <= assignVal && assignVal < (1 << 14)) { + bb.appendBits(2, 2); + bb.appendBits(assignVal, 14); + } else if ((1 << 14) <= assignVal && assignVal < 1000000) { + bb.appendBits(6, 3); + bb.appendBits(assignVal, 21); + } else + throw new IllegalArgumentException("ECI assignment value out of range"); + return new QrSegment(Mode.ECI, 0, bb.data, bb.bitLength); + } + + + public static boolean isNumeric(String text) { + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c < '0' || c > '9') + return false; + } + return true; + } + + + public static boolean isAlphanumeric(String text) { + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c >= ALPHANUMERIC_MAP.length || ALPHANUMERIC_MAP[c] == -1) + return false; + } + return true; + } + + + + /*---- Instance fields ----*/ + + public final Mode mode; + + public final int numChars; + + final int[] data; + + final int bitLength; + + + /*---- Constructor ----*/ + + public QrSegment(Mode md, int numCh, int[] data, int bitLen) { + Objects.requireNonNull(md); + Objects.requireNonNull(data); + if (numCh < 0 || bitLen < 0 || bitLen > data.length * 32) + throw new IllegalArgumentException("Invalid value"); + mode = md; + numChars = numCh; + this.data = data; + bitLength = bitLen; + } + + + + /*---- Constants ----*/ + + private static final int[] ALPHANUMERIC_MAP; + + static { + final String ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + int maxCh = -1; + for (int i = 0; i < ALPHANUMERIC_CHARSET.length(); i++) + maxCh = Math.max(ALPHANUMERIC_CHARSET.charAt(i), maxCh); + ALPHANUMERIC_MAP = new int[maxCh + 1]; + Arrays.fill(ALPHANUMERIC_MAP, -1); + for (int i = 0; i < ALPHANUMERIC_CHARSET.length(); i++) + ALPHANUMERIC_MAP[ALPHANUMERIC_CHARSET.charAt(i)] = i; + } + + + + /*---- Public helper enumeration ----*/ + + public enum Mode { + + /*-- Constants --*/ + + NUMERIC (0x1, 10, 12, 14), + ALPHANUMERIC(0x2, 9, 11, 13), + BYTE (0x4, 8, 16, 16), + KANJI (0x8, 8, 10, 12), + ECI (0x7, 0, 0, 0); + + + /*-- Fields --*/ + + final int modeBits; + + private final int[] numBitsCharCount; + + + /*-- Constructor --*/ + + private Mode(int mode, int... ccbits) { + this.modeBits = mode; + numBitsCharCount = ccbits; + } + + + /*-- Method --*/ + + int numCharCountBits(int ver) { + if ( 1 <= ver && ver <= 9) return numBitsCharCount[0]; + else if (10 <= ver && ver <= 26) return numBitsCharCount[1]; + else if (27 <= ver && ver <= 40) return numBitsCharCount[2]; + else throw new IllegalArgumentException("Version number out of range"); + } + + } + +} From 0c637b5705d41c8b9a8e2d612007ea289b5233c2 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Thu, 23 Nov 2017 05:28:55 +0000 Subject: [PATCH 05/85] Implemented QR template class. --- src/io/nayuki/fastqrcodegen/QrTemplate.java | 316 ++++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 src/io/nayuki/fastqrcodegen/QrTemplate.java diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java new file mode 100644 index 0000000..492e58c --- /dev/null +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -0,0 +1,316 @@ +/* + * Fast QR Code generator library + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.fastqrcodegen; + +import java.lang.ref.SoftReference; + + +final class QrTemplate { + + /*---- Factory members ----*/ + + public static QrTemplate getInstance(int version) { + if (version < MIN_VERSION || version > MAX_VERSION) + throw new IllegalArgumentException("Version out of range"); + + while (true) { + synchronized(cache) { + SoftReference ref = cache[version]; + if (ref != null) { + QrTemplate result = ref.get(); + if (result != null) + return result; + cache[version] = null; + } + + if (!isPending[version]) { + isPending[version] = true; + break; + } + + try { + cache.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + QrTemplate tpl = new QrTemplate(version); + synchronized(cache) { + cache[version] = new SoftReference<>(tpl); + isPending[version] = false; + cache.notifyAll(); + } + return tpl; + } + + + private static final int MIN_VERSION = 1; + private static final int MAX_VERSION = 40; + + @SuppressWarnings("unchecked") + private static final SoftReference[] cache = new SoftReference[MAX_VERSION + 1]; + + private static final boolean[] isPending = new boolean[MAX_VERSION + 1]; + + + + /*---- Instance members ----*/ + + private final int version; + private final int size; + + final int[] template; + final int[][] masks; + final int[] dataOutputBitIndexes; + + private int[] isFunction; // Discarded at end of constructor + + + private QrTemplate(int ver) { + if (ver < MIN_VERSION || ver > MAX_VERSION) + throw new IllegalArgumentException("Version out of range"); + version = ver; + size = version * 4 + 17; + template = new int[(size * size + 31) / 32]; + isFunction = new int[template.length]; + + drawFunctionPatterns(); // Reads and writes fields + masks = generateMasks(); // Reads fields, returns array + dataOutputBitIndexes = generateZigzagScan(); // Reads fields, returns array + isFunction = null; + } + + + private void drawFunctionPatterns() { + // Draw horizontal and vertical timing patterns + for (int i = 0; i < size; i++) { + darkenFunctionModule(6, i, ~i & 1); + darkenFunctionModule(i, 6, ~i & 1); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + drawFinderPattern(3, 3); + drawFinderPattern(size - 4, 3); + drawFinderPattern(3, size - 4); + + // Draw numerous alignment patterns + int[] alignPatPos = getAlignmentPatternPositions(version); + int numAlign = alignPatPos.length; + for (int i = 0; i < numAlign; i++) { + for (int j = 0; j < numAlign; j++) { + if (i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0) + continue; // Skip the three finder corners + else + drawAlignmentPattern(alignPatPos[i], alignPatPos[j]); + } + } + + // Draw configuration data + drawDummyFormatBits(); + drawVersion(); + } + + + // Draws two blank copies of the format bits. + private void drawDummyFormatBits() { + // Draw first copy + for (int i = 0; i <= 5; i++) + darkenFunctionModule(8, i, 0); + darkenFunctionModule(8, 7, 0); + darkenFunctionModule(8, 8, 0); + darkenFunctionModule(7, 8, 0); + for (int i = 9; i < 15; i++) + darkenFunctionModule(14 - i, 8, 0); + + // Draw second copy + for (int i = 0; i <= 7; i++) + darkenFunctionModule(size - 1 - i, 8, 0); + for (int i = 8; i < 15; i++) + darkenFunctionModule(8, size - 15 + i, 0); + darkenFunctionModule(8, size - 8, 1); + } + + + // Draws two copies of the version bits (with its own error correction code), + // based on this object's version field (which only has an effect for 7 <= version <= 40). + private void drawVersion() { + if (version < 7) + return; + + // Calculate error correction code and pack bits + int rem = version; // version is uint6, in the range [7, 40] + for (int i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >>> 11) * 0x1F25); + int data = version << 12 | rem; // uint18 + if (data >>> 18 != 0) + throw new AssertionError(); + + // Draw two copies + for (int i = 0; i < 18; i++) { + int bit = (data >>> i) & 1; + int a = size - 11 + i % 3, b = i / 3; + darkenFunctionModule(a, b, bit); + darkenFunctionModule(b, a, bit); + } + } + + + // Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). + private void drawFinderPattern(int x, int y) { + for (int i = -4; i <= 4; i++) { + for (int j = -4; j <= 4; j++) { + int dist = Math.max(Math.abs(i), Math.abs(j)); // Chebyshev/infinity norm + int xx = x + j, yy = y + i; + if (0 <= xx && xx < size && 0 <= yy && yy < size) + darkenFunctionModule(xx, yy, (dist != 2 && dist != 4) ? 1 : 0); + } + } + } + + + // Draws a 5*5 alignment pattern, with the center module at (x, y). + private void drawAlignmentPattern(int x, int y) { + for (int i = -2; i <= 2; i++) { + for (int j = -2; j <= 2; j++) + darkenFunctionModule(x + j, y + i, (Math.max(Math.abs(i), Math.abs(j)) != 1) ? 1 : 0); + } + } + + + private int[][] generateMasks() { + int[][] result = new int[8][template.length]; + for (int mask = 0; mask < result.length; mask++) { + int[] maskModules = result[mask]; + for (int y = 0, i = 0; y < size; y++) { + for (int x = 0; x < size; x++, i++) { + boolean invert; + switch (mask) { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (x / 3 + y / 2) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + default: throw new AssertionError(); + } + int bit = (invert ? 1 : 0) & ~getModule(isFunction, x, y); + maskModules[i >>> 5] |= bit << i; + } + } + } + return result; + } + + + private int[] generateZigzagScan() { + int[] result = new int[getNumRawDataModules(version) / 8 * 8]; + int i = 0; // Bit index into the data + for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair + if (right == 6) + right = 5; + for (int vert = 0; vert < size; vert++) { // Vertical counter + for (int j = 0; j < 2; j++) { + int x = right - j; // Actual x coordinate + boolean upward = ((right + 1) & 2) == 0; + int y = upward ? size - 1 - vert : vert; // Actual y coordinate + if (getModule(isFunction, x, y) == 0 && i < result.length) { + result[i] = y * size + x; + i++; + } + } + } + } + if (i != result.length) + throw new AssertionError(); + return result; + } + + + private int getModule(int[] grid, int x, int y) { + assert 0 <= x && x < size; + assert 0 <= y && y < size; + int i = y * size + x; + return (grid[i >>> 5] >>> i) & 1; + } + + + private void darkenFunctionModule(int x, int y, int enable) { + assert 0 <= x && x < size; + assert 0 <= y && y < size; + assert enable == 0 || enable == 1; + int i = y * size + x; + template[i >>> 5] |= enable << i; + isFunction[i >>> 5] |= 1 << i; + } + + + /*---- Private static helper functions ----*/ + + // Returns a set of positions of the alignment patterns in ascending order. These positions are + // used on both the x and y axes. Each value in the resulting array is in the range [0, 177). + // This stateless pure function could be implemented as table of 40 variable-length lists of unsigned bytes. + private static int[] getAlignmentPatternPositions(int ver) { + if (ver < MIN_VERSION || ver > MAX_VERSION) + throw new IllegalArgumentException("Version number out of range"); + else if (ver == 1) + return new int[]{}; + else { + int numAlign = ver / 7 + 2; + int step; + if (ver != 32) { + // ceil((size - 13) / (2*numAlign - 2)) * 2 + step = (ver * 4 + numAlign * 2 + 1) / (2 * numAlign - 2) * 2; + } else // C-C-C-Combo breaker! + step = 26; + + int[] result = new int[numAlign]; + result[0] = 6; + for (int i = result.length - 1, pos = ver * 4 + 10; i >= 1; i--, pos -= step) + result[i] = pos; + return result; + } + } + + + // Returns the number of data bits that can be stored in a QR Code of the given version number, after + // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. + // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. + private static int getNumRawDataModules(int ver) { + if (ver < MIN_VERSION || ver > MAX_VERSION) + throw new IllegalArgumentException("Version number out of range"); + int result = (16 * ver + 128) * ver + 64; + if (ver >= 2) { + int numAlign = ver / 7 + 2; + result -= (25 * numAlign - 10) * numAlign - 55; + if (ver >= 7) + result -= 18 * 2; // Subtract version information + } + return result; + } + +} From 2fc396a607df86519a4bdea29f0fef9304118a0b Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Thu, 23 Nov 2017 06:13:10 +0000 Subject: [PATCH 06/85] Implemented some supporting methods and changes for BitBuffer, QrSegment, QrTemplate. --- src/io/nayuki/fastqrcodegen/BitBuffer.java | 42 +++++++++++++++++++++ src/io/nayuki/fastqrcodegen/QrSegment.java | 21 +++++++++++ src/io/nayuki/fastqrcodegen/QrTemplate.java | 2 +- 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java index a73158b..875d1f8 100644 --- a/src/io/nayuki/fastqrcodegen/BitBuffer.java +++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java @@ -24,6 +24,7 @@ package io.nayuki.fastqrcodegen; import java.util.Arrays; +import java.util.Objects; final class BitBuffer { @@ -54,6 +55,16 @@ final class BitBuffer { } + public byte[] getBytes() { + if (bitLength % 8 != 0) + throw new IllegalStateException("Data is not a whole number of bytes"); + byte[] result = new byte[bitLength / 8]; + for (int i = 0; i < result.length; i++) + result[i] = (byte)(data[i >>> 2] >>> (~i << 3)); + return result; + } + + public void appendBits(int val, int len) { if (len < 0 || len > 31 || val >>> len != 0) throw new IllegalArgumentException("Value out of range"); @@ -76,4 +87,35 @@ final class BitBuffer { bitLength += len; } + + public void appendBits(int[] vals, int len) { + Objects.requireNonNull(vals); + if (len == 0) + return; + if (len < 0 || len > vals.length * 32) + throw new IllegalArgumentException("Value out of range"); + int wholeWords = len / 32; + int tailBits = len % 32; + if (tailBits > 0 && vals[wholeWords] << tailBits != 0) + throw new IllegalArgumentException("Last word must have low bits clear"); + + while (bitLength + len > data.length * 32) + data = Arrays.copyOf(data, data.length * 2); + + int shift = bitLength % 32; + if (shift == 0) { + System.arraycopy(vals, 0, data, bitLength / 32, (len + 31) / 32); + bitLength += len; + } else { + for (int i = 0; i < wholeWords; i++) { + int word = vals[i]; + data[bitLength >>> 5] |= word >>> shift; + bitLength += 32; + data[bitLength >>> 5] = word << (32 - shift); + } + if (tailBits > 0) + appendBits(vals[wholeWords] >>> (32 - tailBits), tailBits); + } + } + } diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java index 44a9689..91f0545 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegment.java +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -167,6 +167,27 @@ public final class QrSegment { } + // Package-private helper function. + static int getTotalBits(List segs, int version) { + Objects.requireNonNull(segs); + if (version < 1 || version > 40) + throw new IllegalArgumentException("Version number out of range"); + + long result = 0; + for (QrSegment seg : segs) { + Objects.requireNonNull(seg); + int ccbits = seg.mode.numCharCountBits(version); + // Fail if segment length value doesn't fit in the length field's bit-width + if (seg.numChars >= (1 << ccbits)) + return -1; + result += 4L + ccbits + seg.bitLength; + if (result > Integer.MAX_VALUE) + return -1; + } + return (int)result; + } + + /*---- Constants ----*/ diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 492e58c..b60ce81 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -300,7 +300,7 @@ final class QrTemplate { // Returns the number of data bits that can be stored in a QR Code of the given version number, after // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. - private static int getNumRawDataModules(int ver) { + static int getNumRawDataModules(int ver) { if (ver < MIN_VERSION || ver > MAX_VERSION) throw new IllegalArgumentException("Version number out of range"); int result = (16 * ver + 128) * ver + 64; From 68fdc6024e93d3161b6b61387212af116b607997 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Thu, 23 Nov 2017 06:38:25 +0000 Subject: [PATCH 07/85] Fixed a bug in the alphanumeric mode encoder function. --- src/io/nayuki/fastqrcodegen/QrSegment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java index 91f0545..d897a2c 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegment.java +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -75,7 +75,7 @@ public final class QrSegment { char c = text.charAt(i); if (c >= ALPHANUMERIC_MAP.length || ALPHANUMERIC_MAP[c] == -1) throw new IllegalArgumentException("String contains unencodable characters in alphanumeric mode"); - accumData = accumData * 10 + ALPHANUMERIC_MAP[c]; + accumData = accumData * 45 + ALPHANUMERIC_MAP[c]; accumCount++; if (accumCount == 2) { bb.appendBits(accumData, 11); From 2ea993a381da63b5c66957236b090820a312345a Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Thu, 23 Nov 2017 06:58:16 +0000 Subject: [PATCH 08/85] Implemented principal QR Code class, added demo program. --- src/io/nayuki/fastqrcodegen/QrCode.java | 515 ++++++++++++++++++ .../fastqrcodegen/QrCodeGeneratorDemo.java | 190 +++++++ 2 files changed, 705 insertions(+) create mode 100644 src/io/nayuki/fastqrcodegen/QrCode.java create mode 100644 src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java new file mode 100644 index 0000000..750d4f9 --- /dev/null +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -0,0 +1,515 @@ +/* + * Fast QR Code generator library + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.fastqrcodegen; + +import java.awt.image.BufferedImage; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + + +public final class QrCode { + + /*---- Public static factory functions ----*/ + + public static QrCode encodeText(String text, Ecc ecl) { + Objects.requireNonNull(text); + Objects.requireNonNull(ecl); + List segs = QrSegment.makeSegments(text); + return encodeSegments(segs, ecl); + } + + + public static QrCode encodeBinary(byte[] data, Ecc ecl) { + Objects.requireNonNull(data); + Objects.requireNonNull(ecl); + QrSegment seg = QrSegment.makeBytes(data); + return encodeSegments(Arrays.asList(seg), ecl); + } + + + public static QrCode encodeSegments(List segs, Ecc ecl) { + return encodeSegments(segs, ecl, MIN_VERSION, MAX_VERSION, -1, true); + } + + + public static QrCode encodeSegments(List segs, Ecc ecl, int minVersion, int maxVersion, int mask, boolean boostEcl) { + Objects.requireNonNull(segs); + Objects.requireNonNull(ecl); + if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) + throw new IllegalArgumentException("Invalid value"); + + // Find the minimal version number to use + int version, dataUsedBits; + for (version = minVersion; ; version++) { + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available + dataUsedBits = QrSegment.getTotalBits(segs, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + if (version >= maxVersion) // All versions in the range could not fit the given data + throw new IllegalArgumentException("Data too long"); + } + if (dataUsedBits == -1) + throw new AssertionError(); + + // Increase the error correction level while the data still fits in the current version number + for (Ecc newEcl : Ecc.values()) { + if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) + ecl = newEcl; + } + + // Create the data bit string by concatenating all segments + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; + BitBuffer bb = new BitBuffer(); + for (QrSegment seg : segs) { + bb.appendBits(seg.mode.modeBits, 4); + bb.appendBits(seg.numChars, seg.mode.numCharCountBits(version)); + bb.appendBits(seg.data, seg.bitLength); + } + + // Add terminator and pad up to a byte if applicable + bb.appendBits(0, Math.min(4, dataCapacityBits - bb.bitLength)); + bb.appendBits(0, (8 - bb.bitLength % 8) % 8); + + // Pad with alternate bytes until data capacity is reached + for (int padByte = 0xEC; bb.bitLength < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + bb.appendBits(padByte, 8); + if (bb.bitLength % 8 != 0) + throw new AssertionError(); + + // Create the QR Code symbol + return new QrCode(version, ecl, bb.getBytes(), mask); + } + + + + /*---- Public constants ----*/ + + public static final int MIN_VERSION = 1; + public static final int MAX_VERSION = 40; + + + + /*---- Instance fields ----*/ + + public final int version; + + public final int size; + + public final Ecc errorCorrectionLevel; + + public final int mask; + + private final int[] modules; + + + + /*---- Constructors ----*/ + + public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) { + // Check arguments + Objects.requireNonNull(ecl); + if (ver < MIN_VERSION || ver > MAX_VERSION || mask < -1 || mask > 7) + throw new IllegalArgumentException("Value out of range"); + Objects.requireNonNull(dataCodewords); + + // Initialize fields + version = ver; + size = ver * 4 + 17; + errorCorrectionLevel = ecl; + + QrTemplate tpl = QrTemplate.getInstance(ver); + modules = tpl.template.clone(); + + // Draw function patterns, draw all codewords, do masking + byte[] allCodewords = appendErrorCorrection(dataCodewords); + drawCodewords(tpl.dataOutputBitIndexes, allCodewords); + this.mask = handleConstructorMasking(tpl.masks, mask); + } + + + + /*---- Public instance methods ----*/ + + /** + * Returns the color of the module (pixel) at the specified coordinates, which is either + * false for white or true for black. The top left corner has the coordinates (x=0, y=0). + * If the specified coordinates are out of bounds, then false (white) is returned. + * @param x the x coordinate, where 0 is the left edge and size−1 is the right edge + * @param y the y coordinate, where 0 is the top edge and size−1 is the bottom edge + * @return the module's color, which is either false (white) or true (black) + */ + public boolean getModule(int x, int y) { + return 0 <= x && x < size && 0 <= y && y < size + && getModuleUnchecked(x, y) != 0; + } + + + /** + * Returns a new image object representing this QR Code, with the specified module scale and number + * of border modules. For example, the arguments scale=10, border=4 means to pad the QR Code symbol + * with 4 white border modules on all four edges, then use 10*10 pixels to represent each module. + * The resulting image only contains the hex colors 000000 and FFFFFF. + * @param scale the module scale factor, which must be positive + * @param border the number of border modules to add, which must be non-negative + * @return an image representing this QR Code, with padding and scaling + * @throws IllegalArgumentException if the scale or border is out of range + */ + public BufferedImage toImage(int scale, int border) { + if (scale <= 0 || border < 0) + throw new IllegalArgumentException("Value out of range"); + BufferedImage result = new BufferedImage((size + border * 2) * scale, (size + border * 2) * scale, BufferedImage.TYPE_INT_RGB); + for (int y = 0; y < result.getHeight(); y++) { + for (int x = 0; x < result.getWidth(); x++) { + boolean val = getModule(x / scale - border, y / scale - border); + result.setRGB(x, y, val ? 0x000000 : 0xFFFFFF); + } + } + return result; + } + + + /** + * Based on the specified number of border modules to add as padding, this returns a + * string whose contents represents an SVG XML file that depicts this QR Code symbol. + * Note that Unix newlines (\n) are always used, regardless of the platform. + * @param border the number of border modules to add, which must be non-negative + * @return a string representing this QR Code as an SVG document + */ + public String toSvgString(int border) { + if (border < 0) + throw new IllegalArgumentException("Border must be non-negative"); + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("\n"); + sb.append(String.format( + "\n", + size + border * 2)); + sb.append("\t\n"); + sb.append("\t\n"); + sb.append("\n"); + return sb.toString(); + } + + + + /*---- Private helper methods for constructor: Drawing function modules ----*/ + + // Draws two copies of the format bits (with its own error correction code) + // based on the given mask and this object's error correction level field. + private void drawFormatBits(int mask) { + // Calculate error correction code and pack bits + int data = errorCorrectionLevel.formatBits << 3 | mask; // errCorrLvl is uint2, mask is uint3 + int rem = data; + for (int i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >>> 9) * 0x537); + data = data << 10 | rem; + data ^= 0x5412; // uint15 + if (data >>> 15 != 0) + throw new AssertionError(); + + // Draw first copy + for (int i = 0; i <= 5; i++) + setModule(8, i, (data >>> i) & 1); + setModule(8, 7, (data >>> 6) & 1); + setModule(8, 8, (data >>> 7) & 1); + setModule(7, 8, (data >>> 8) & 1); + for (int i = 9; i < 15; i++) + setModule(14 - i, 8, (data >>> i) & 1); + + // Draw second copy + for (int i = 0; i <= 7; i++) + setModule(size - 1 - i, 8, (data >>> i) & 1); + for (int i = 8; i < 15; i++) + setModule(8, size - 15 + i, (data >>> i) & 1); + setModule(8, size - 8, 1); + } + + + private void setModule(int x, int y, int black) { + assert 0 <= x && x < size; + assert 0 <= y && y < size; + int i = y * size + x; + if (black == 0) + modules[i >>> 5] &= ~(1 << i); + else if (black == 1) + modules[i >>> 5] |= 1 << i; + else + throw new IllegalArgumentException(); + } + + + private int getModuleUnchecked(int x, int y) { + int i = y * size + x; + return (modules[i >>> 5] >>> i) & 1; + } + + + /*---- Private helper methods for constructor: Codewords and masking ----*/ + + // Returns a new byte string representing the given data with the appropriate error correction + // codewords appended to it, based on this object's version and error correction level. + private byte[] appendErrorCorrection(byte[] data) { + if (data.length != getNumDataCodewords(version, errorCorrectionLevel)) + throw new IllegalArgumentException(); + + // Calculate parameter numbers + int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[errorCorrectionLevel.ordinal()][version]; + int blockEccLen = ECC_CODEWORDS_PER_BLOCK[errorCorrectionLevel.ordinal()][version]; + int rawCodewords = QrTemplate.getNumRawDataModules(version) / 8; + int numShortBlocks = numBlocks - rawCodewords % numBlocks; + int shortBlockLen = rawCodewords / numBlocks; + + // Split data into blocks and append ECC to each block + byte[][] blocks = new byte[numBlocks][]; + ReedSolomonGenerator rs = ReedSolomonGenerator.getInstance(blockEccLen); + byte[] ecc = new byte[blockEccLen]; + for (int i = 0, k = 0; i < numBlocks; i++) { + byte[] dat = Arrays.copyOfRange(data, k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1)); + byte[] block = Arrays.copyOf(dat, shortBlockLen + 1); + k += dat.length; + rs.getRemainder(dat, ecc); + System.arraycopy(ecc, 0, block, block.length - blockEccLen, ecc.length); + blocks[i] = block; + } + + // Interleave (not concatenate) the bytes from every block into a single sequence + byte[] result = new byte[rawCodewords]; + for (int i = 0, k = 0; i < blocks[0].length; i++) { + for (int j = 0; j < blocks.length; j++) { + // Skip the padding byte in short blocks + if (i != shortBlockLen - blockEccLen || j >= numShortBlocks) { + result[k] = blocks[j][i]; + k++; + } + } + } + return result; + } + + + // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire + // data area of this QR Code symbol. Function modules need to be marked off before this is called. + private void drawCodewords(int[] dataOutputBitIndexes, byte[] allCodewords) { + Objects.requireNonNull(dataOutputBitIndexes); + Objects.requireNonNull(allCodewords); + if (allCodewords.length * 8 != dataOutputBitIndexes.length) + throw new IllegalArgumentException(); + for (int i = 0; i < dataOutputBitIndexes.length; i++) { + int j = dataOutputBitIndexes[i]; + int bit = (allCodewords[i >>> 3] >>> (~i & 7)) & 1; + modules[j >>> 5] |= bit << j; + } + } + + + // XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical + // properties, calling applyMask(m) twice with the same value is equivalent to no change at all. + // This means it is possible to apply a mask, undo it, and try another mask. Note that a final + // well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). + private void applyMask(int[] mask) { + if (mask.length != modules.length) + throw new IllegalArgumentException(); + for (int i = 0; i < mask.length; i++) + modules[i] ^= mask[i]; + } + + + // A messy helper function for the constructors. 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. + // This method applies and returns the actual mask chosen, from 0 to 7. + private int handleConstructorMasking(int[][] masks, int mask) { + if (mask == -1) { // Automatically choose best mask + int minPenalty = Integer.MAX_VALUE; + for (int i = 0; i < 8; i++) { + drawFormatBits(i); + applyMask(masks[i]); + int penalty = getPenaltyScore(); + if (penalty < minPenalty) { + mask = i; + minPenalty = penalty; + } + applyMask(masks[i]); // Undoes the mask due to XOR + } + } + if (mask < 0 || mask > 7) + throw new AssertionError(); + drawFormatBits(mask); // Overwrite old format bits + applyMask(masks[mask]); // Apply the final choice of mask + return mask; // The caller shall assign this value to the final-declared field + } + + + // Calculates and returns the penalty score based on state of this QR Code's current modules. + // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. + private int getPenaltyScore() { + int result = 0; + + // Adjacent modules in row having same color + for (int y = 0; y < size; y++) { + int colorX = 0; + for (int x = 0, runX = 0; x < size; x++) { + if (x == 0 || getModuleUnchecked(x, y) != colorX) { + colorX = getModuleUnchecked(x, y); + runX = 1; + } else { + runX++; + if (runX == 5) + result += PENALTY_N1; + else if (runX > 5) + result++; + } + } + } + // Adjacent modules in column having same color + for (int x = 0; x < size; x++) { + int colorY = 0; + for (int y = 0, runY = 0; y < size; y++) { + if (y == 0 || getModuleUnchecked(x, y) != colorY) { + colorY = getModuleUnchecked(x, y); + runY = 1; + } else { + runY++; + if (runY == 5) + result += PENALTY_N1; + else if (runY > 5) + result++; + } + } + } + + // 2*2 blocks of modules having same color + for (int y = 0; y < size - 1; y++) { + for (int x = 0; x < size - 1; x++) { + int color = getModuleUnchecked(x, y); + if ( color == getModuleUnchecked(x + 1, y) && + color == getModuleUnchecked(x, y + 1) && + color == getModuleUnchecked(x + 1, y + 1)) + result += PENALTY_N2; + } + } + + // Finder-like pattern in rows + for (int y = 0; y < size; y++) { + for (int x = 0, bits = 0; x < size; x++) { + bits = ((bits << 1) & 0x7FF) | getModuleUnchecked(x, y); + if (x >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated + result += PENALTY_N3; + } + } + // Finder-like pattern in columns + for (int x = 0; x < size; x++) { + for (int y = 0, bits = 0; y < size; y++) { + bits = ((bits << 1) & 0x7FF) | getModuleUnchecked(x, y); + if (y >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated + result += PENALTY_N3; + } + } + + // Balance of black and white modules + int black = 0; + for (int x : modules) + black += Integer.bitCount(x); + int total = size * size; + // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% + for (int k = 0; black*20 < (9-k)*total || black*20 > (11+k)*total; k++) + result += PENALTY_N4; + return result; + } + + + + /*---- Private static helper functions ----*/ + + // 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. + static int getNumDataCodewords(int ver, Ecc ecl) { + if (ver < MIN_VERSION || ver > MAX_VERSION) + throw new IllegalArgumentException("Version number out of range"); + return QrTemplate.getNumRawDataModules(ver) / 8 + - ECC_CODEWORDS_PER_BLOCK[ecl.ordinal()][ver] + * NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal()][ver]; + } + + + /*---- Private tables of constants ----*/ + + // For use in getPenaltyScore(), when evaluating which mask is best. + private static final int PENALTY_N1 = 3; + private static final int PENALTY_N2 = 3; + private static final int PENALTY_N3 = 40; + private static final int PENALTY_N4 = 10; + + + private static final byte[][] ECC_CODEWORDS_PER_BLOCK = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low + {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium + {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile + {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High + }; + + private static final byte[][] NUM_ERROR_CORRECTION_BLOCKS = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 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}, // Low + {-1, 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}, // Medium + {-1, 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}, // Quartile + {-1, 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}, // High + }; + + + + /*---- Public helper enumeration ----*/ + + public enum Ecc { + // These enum constants must be declared in ascending order of error protection, + // for the sake of the implicit ordinal() method and values() function. + LOW(1), MEDIUM(0), QUARTILE(3), HIGH(2); + + // In the range 0 to 3 (unsigned 2-bit integer). + final int formatBits; + + // Constructor. + private Ecc(int fb) { + formatBits = fb; + } + } + +} diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java new file mode 100644 index 0000000..4d4bff5 --- /dev/null +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java @@ -0,0 +1,190 @@ +/* + * Fast QR Code generator demo + * + * Run this command-line program with no arguments. The program creates/overwrites a bunch of + * PNG and SVG files in the current working directory to demonstrate the creation of QR Codes. + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.fastqrcodegen; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import javax.imageio.ImageIO; + + +public final class QrCodeGeneratorDemo { + + // The main application program. + public static void main(String[] args) throws IOException { + doBasicDemo(); + doVarietyDemo(); + doSegmentDemo(); + doMaskDemo(); + } + + + + /*---- Demo suite ----*/ + + // Creates a single QR Code, then writes it to a PNG file and an SVG file. + private static void doBasicDemo() throws IOException { + String text = "Hello, world!"; // User-supplied Unicode text + QrCode.Ecc errCorLvl = QrCode.Ecc.LOW; // Error correction level + + QrCode qr = QrCode.encodeText(text, errCorLvl); // Make the QR Code symbol + + BufferedImage img = qr.toImage(10, 4); // Convert to bitmap image + File imgFile = new File("hello-world-QR.png"); // File path for output + ImageIO.write(img, "png", imgFile); // Write image to file + + String svg = qr.toSvgString(4); // Convert to SVG XML code + try (Writer out = new OutputStreamWriter( + new FileOutputStream("hello-world-QR.svg"), + StandardCharsets.UTF_8)) { + out.write(svg); // Create/overwrite file and write SVG data + } + } + + + // Creates a variety of QR Codes that exercise different features of the library, and writes each one to file. + private static void doVarietyDemo() throws IOException { + QrCode qr; + + // Numeric mode encoding (3.33 bits per digit) + qr = QrCode.encodeText("314159265358979323846264338327950288419716939937510", QrCode.Ecc.MEDIUM); + writePng(qr.toImage(13, 1), "pi-digits-QR.png"); + + // Alphanumeric mode encoding (5.5 bits per character) + qr = QrCode.encodeText("DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/", QrCode.Ecc.HIGH); + writePng(qr.toImage(10, 2), "alphanumeric-QR.png"); + + // Unicode text as UTF-8 + qr = QrCode.encodeText("こんにちwa、世界! αβγδ", QrCode.Ecc.QUARTILE); + writePng(qr.toImage(10, 3), "unicode-QR.png"); + + // Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland) + qr = QrCode.encodeText( + "Alice was beginning to get very tired of sitting by her sister on the bank, " + + "and of having nothing to do: once or twice she had peeped into the book her sister was reading, " + + "but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice " + + "'without pictures or conversations?' So she was considering in her own mind (as well as she could, " + + "for the hot day made her feel very sleepy and stupid), whether the pleasure of making a " + + "daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly " + + "a White Rabbit with pink eyes ran close by her.", QrCode.Ecc.HIGH); + writePng(qr.toImage(6, 10), "alice-wonderland-QR.png"); + } + + + // Creates QR Codes with manually specified segments for better compactness. + private static void doSegmentDemo() throws IOException { + QrCode qr; + List segs; + + // Illustration "silver" + String silver0 = "THE SQUARE ROOT OF 2 IS 1."; + String silver1 = "41421356237309504880168872420969807856967187537694807317667973799"; + qr = QrCode.encodeText(silver0 + silver1, QrCode.Ecc.LOW); + writePng(qr.toImage(10, 3), "sqrt2-monolithic-QR.png"); + + segs = Arrays.asList( + QrSegment.makeAlphanumeric(silver0), + QrSegment.makeNumeric(silver1)); + qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); + writePng(qr.toImage(10, 3), "sqrt2-segmented-QR.png"); + + // Illustration "golden" + String golden0 = "Golden ratio φ = 1."; + String golden1 = "6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911374"; + String golden2 = "......"; + qr = QrCode.encodeText(golden0 + golden1 + golden2, QrCode.Ecc.LOW); + writePng(qr.toImage(8, 5), "phi-monolithic-QR.png"); + + segs = Arrays.asList( + QrSegment.makeBytes(golden0.getBytes(StandardCharsets.UTF_8)), + QrSegment.makeNumeric(golden1), + QrSegment.makeAlphanumeric(golden2)); + qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); + writePng(qr.toImage(8, 5), "phi-segmented-QR.png"); + + // Illustration "Madoka": kanji, kana, Greek, Cyrillic, full-width Latin characters + String madoka = "「魔法少女まどか☆マギカ」って、 ИАИ desu κα?"; + qr = QrCode.encodeText(madoka, QrCode.Ecc.LOW); + writePng(qr.toImage(9, 4), "madoka-utf8-QR.png"); + + int[] kanjiChars = { // Kanji mode encoding (13 bits per character) + 0x0035, 0x1002, 0x0FC0, 0x0AED, 0x0AD7, + 0x015C, 0x0147, 0x0129, 0x0059, 0x01BD, + 0x018D, 0x018A, 0x0036, 0x0141, 0x0144, + 0x0001, 0x0000, 0x0249, 0x0240, 0x0249, + 0x0000, 0x0104, 0x0105, 0x0113, 0x0115, + 0x0000, 0x0208, 0x01FF, 0x0008, + }; + BitBuffer bb = new BitBuffer(); + for (int c : kanjiChars) + bb.appendBits(c, 13); + segs = Arrays.asList(new QrSegment(QrSegment.Mode.KANJI, kanjiChars.length, bb.data, bb.bitLength)); + qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); + writePng(qr.toImage(9, 4), "madoka-kanji-QR.png"); + } + + + // Creates QR Codes with the same size and contents but different mask patterns. + private static void doMaskDemo() throws IOException { + QrCode qr; + List segs; + + // Project Nayuki URL + segs = QrSegment.makeSegments("https://www.nayuki.io/"); + qr = QrCode.encodeSegments(segs, QrCode.Ecc.HIGH, QrCode.MIN_VERSION, QrCode.MAX_VERSION, -1, true); // Automatic mask + writePng(qr.toImage(8, 6), "project-nayuki-automask-QR.png"); + qr = QrCode.encodeSegments(segs, QrCode.Ecc.HIGH, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 3, true); // Force mask 3 + writePng(qr.toImage(8, 6), "project-nayuki-mask3-QR.png"); + + // Chinese text as UTF-8 + segs = QrSegment.makeSegments("維基百科(Wikipedia,聆聽i/ˌwɪkᵻˈpiːdi.ə/)是一個自由內容、公開編輯且多語言的網路百科全書協作計畫"); + qr = QrCode.encodeSegments(segs, QrCode.Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 0, true); // Force mask 0 + writePng(qr.toImage(10, 3), "unicode-mask0-QR.png"); + qr = QrCode.encodeSegments(segs, QrCode.Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 1, true); // Force mask 1 + writePng(qr.toImage(10, 3), "unicode-mask1-QR.png"); + qr = QrCode.encodeSegments(segs, QrCode.Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 5, true); // Force mask 5 + writePng(qr.toImage(10, 3), "unicode-mask5-QR.png"); + qr = QrCode.encodeSegments(segs, QrCode.Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 7, true); // Force mask 7 + writePng(qr.toImage(10, 3), "unicode-mask7-QR.png"); + } + + + + /*---- Utilities ----*/ + + // Helper function to reduce code duplication. + private static void writePng(BufferedImage img, String filepath) throws IOException { + ImageIO.write(img, "png", new File(filepath)); + } + +} From e493029731bdadbcb7ff402a04f9f407251b88b8 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Thu, 23 Nov 2017 17:23:21 +0000 Subject: [PATCH 09/85] Added a batch-testing worker program. --- .../fastqrcodegen/QrCodeGeneratorWorker.java | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java new file mode 100644 index 0000000..cd04eb5 --- /dev/null +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java @@ -0,0 +1,103 @@ +/* + * QR Code generator test worker + * + * This program reads data and encoding parameters from standard input and writes + * QR Code bitmaps to standard output. The I/O format is one integer per line. + * Run with no command line arguments. The program is intended for automated + * batch testing of end-to-end functionality of this QR Code generator library. + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.fastqrcodegen; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; + + +public final class QrCodeGeneratorWorker { + + public static void main(String[] args) { + // Set up input stream and start loop + try (Scanner input = new Scanner(System.in, "US-ASCII")) { + input.useDelimiter("\r\n|\n|\r"); + while (processCase(input)); + } + } + + + private static boolean processCase(Scanner input) { + // Read data length or exit + int length = input.nextInt(); + if (length == -1) + return false; + if (length > Short.MAX_VALUE) + throw new RuntimeException(); + + // Read data bytes + boolean isAscii = true; + byte[] data = new byte[length]; + for (int i = 0; i < data.length; i++) { + int b = input.nextInt(); + if (b < 0 || b > 255) + throw new RuntimeException(); + data[i] = (byte)b; + isAscii &= b < 128; + } + + // Read encoding parameters + int errCorLvl = input.nextInt(); + int minVersion = input.nextInt(); + int maxVersion = input.nextInt(); + int mask = input.nextInt(); + int boostEcl = input.nextInt(); + if (!(0 <= errCorLvl && errCorLvl <= 3) || !(-1 <= mask && mask <= 7) || (boostEcl >>> 1) != 0 + || !(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION)) + throw new RuntimeException(); + + // Make segments for encoding + List segs; + if (isAscii) + segs = QrSegment.makeSegments(new String(data, StandardCharsets.US_ASCII)); + else + segs = Arrays.asList(QrSegment.makeBytes(data)); + + + try { // Try to make QR Code symbol + QrCode qr = QrCode.encodeSegments(segs, QrCode.Ecc.values()[errCorLvl], minVersion, maxVersion, mask, boostEcl != 0); + // Print grid of modules + System.out.println(qr.version); + for (int y = 0; y < qr.size; y++) { + for (int x = 0; x < qr.size; x++) + System.out.println(qr.getModule(x, y) ? 1 : 0); + } + + } catch (IllegalArgumentException e) { + if (!e.getMessage().equals("Data too long")) + throw e; + System.out.println(-1); + } + System.out.flush(); + return true; + } + +} From 71cc6576f6066be339a9d9b0bf766620a7750681 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 29 Nov 2017 20:35:41 +0000 Subject: [PATCH 10/85] Updated cache logic to be exception-safe. --- src/io/nayuki/fastqrcodegen/QrTemplate.java | 17 +++++++++++------ .../fastqrcodegen/ReedSolomonGenerator.java | 17 +++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index b60ce81..e218ed0 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -57,13 +57,18 @@ final class QrTemplate { } } - QrTemplate tpl = new QrTemplate(version); - synchronized(cache) { - cache[version] = new SoftReference<>(tpl); - isPending[version] = false; - cache.notifyAll(); + try { + QrTemplate tpl = new QrTemplate(version); + synchronized(cache) { + cache[version] = new SoftReference<>(tpl); + } + return tpl; + } finally { + synchronized(cache) { + isPending[version] = false; + cache.notifyAll(); + } } - return tpl; } diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java index 2a35fce..3ad23f1 100644 --- a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -59,13 +59,18 @@ final class ReedSolomonGenerator { } } - ReedSolomonGenerator rs = new ReedSolomonGenerator(degree); - synchronized(cache) { - cache[degree] = new SoftReference<>(rs); - isPending[degree] = false; - cache.notifyAll(); + try { + ReedSolomonGenerator rs = new ReedSolomonGenerator(degree); + synchronized(cache) { + cache[degree] = new SoftReference<>(rs); + } + return rs; + } finally { + synchronized(cache) { + isPending[degree] = false; + cache.notifyAll(); + } } - return rs; } From 797d5bc3d0511e296d1f3c7f0078120bc6410601 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 29 Nov 2017 20:37:17 +0000 Subject: [PATCH 11/85] Tweaked code to reuse constants. --- src/io/nayuki/fastqrcodegen/QrTemplate.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index e218ed0..b31da45 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -23,6 +23,8 @@ package io.nayuki.fastqrcodegen; +import static io.nayuki.fastqrcodegen.QrCode.MAX_VERSION; +import static io.nayuki.fastqrcodegen.QrCode.MIN_VERSION; import java.lang.ref.SoftReference; @@ -72,9 +74,6 @@ final class QrTemplate { } - private static final int MIN_VERSION = 1; - private static final int MAX_VERSION = 40; - @SuppressWarnings("unchecked") private static final SoftReference[] cache = new SoftReference[MAX_VERSION + 1]; From aef4073a22cfd34be8e04857277c684219c42607 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 29 Nov 2017 20:43:57 +0000 Subject: [PATCH 12/85] Sped up QrCode.getPenaltyScore() by combining and rewriting loops. --- src/io/nayuki/fastqrcodegen/QrCode.java | 98 ++++++++++++------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 750d4f9..9eb2d06 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -378,72 +378,72 @@ public final class QrCode { // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. private int getPenaltyScore() { int result = 0; + int black = 0; - // Adjacent modules in row having same color - for (int y = 0; y < size; y++) { - int colorX = 0; - for (int x = 0, runX = 0; x < size; x++) { - if (x == 0 || getModuleUnchecked(x, y) != colorX) { - colorX = getModuleUnchecked(x, y); - runX = 1; + // Iterate over adjacent pairs of rows + for (int index = 0, downIndex = size, end = size * size; index < end; ) { + int bits = 0; + int downBits = 0; + int runColor = 0; + int runLen = 0; + for (int x = 0; x < size; x++, index++, downIndex++) { + + // Adjacent modules having same color + int bit = (modules[index >>> 5] >>> index) & 1; + if (bit != runColor) { + runColor = bit; + runLen = 1; } else { - runX++; - if (runX == 5) + runLen++; + if (runLen == 5) result += PENALTY_N1; - else if (runX > 5) + else if (runLen > 5) result++; } + + black += bit; + bits = ((bits & 0b1111111111) << 1) | bit; + if (downIndex < end) { + downBits = ((downBits & 1) << 1) | ((modules[downIndex >>> 5] >>> downIndex) & 1); + // 2*2 blocks of modules having same color + if (x >= 1 && (downBits == 0 || downBits == 3) && downBits == (bits & 3)) + result += PENALTY_N2; + } + + // Finder-like pattern + if (x >= 10 && (bits == 0b00001011101 || bits == 0b10111010000)) + result += PENALTY_N3; } } - // Adjacent modules in column having same color + + // Iterate over single columns for (int x = 0; x < size; x++) { - int colorY = 0; - for (int y = 0, runY = 0; y < size; y++) { - if (y == 0 || getModuleUnchecked(x, y) != colorY) { - colorY = getModuleUnchecked(x, y); - runY = 1; + int bits = 0; + int runColor = 0; + int runLen = 0; + for (int y = 0, index = x; y < size; y++, index += size) { + + // Adjacent modules having same color + int bit = (modules[index >>> 5] >>> index) & 1; + if (bit != runColor) { + runColor = bit; + runLen = 1; } else { - runY++; - if (runY == 5) + runLen++; + if (runLen == 5) result += PENALTY_N1; - else if (runY > 5) + else if (runLen > 5) result++; } - } - } - - // 2*2 blocks of modules having same color - for (int y = 0; y < size - 1; y++) { - for (int x = 0; x < size - 1; x++) { - int color = getModuleUnchecked(x, y); - if ( color == getModuleUnchecked(x + 1, y) && - color == getModuleUnchecked(x, y + 1) && - color == getModuleUnchecked(x + 1, y + 1)) - result += PENALTY_N2; - } - } - - // Finder-like pattern in rows - for (int y = 0; y < size; y++) { - for (int x = 0, bits = 0; x < size; x++) { - bits = ((bits << 1) & 0x7FF) | getModuleUnchecked(x, y); - if (x >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated - result += PENALTY_N3; - } - } - // Finder-like pattern in columns - for (int x = 0; x < size; x++) { - for (int y = 0, bits = 0; y < size; y++) { - bits = ((bits << 1) & 0x7FF) | getModuleUnchecked(x, y); - if (y >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated + + // Finder-like pattern + bits = ((bits & 0b1111111111) << 1) | bit; + if (y >= 10 && (bits == 0b00001011101 || bits == 0b10111010000)) result += PENALTY_N3; } } // Balance of black and white modules - int black = 0; - for (int x : modules) - black += Integer.bitCount(x); int total = size * size; // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% for (int k = 0; black*20 < (9-k)*total || black*20 > (11+k)*total; k++) From 9e9890ada9027eb7e31d3b2d9e0a2d1d2f1d3172 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 29 Nov 2017 23:12:14 +0000 Subject: [PATCH 13/85] Inlined a private method. --- src/io/nayuki/fastqrcodegen/QrCode.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 9eb2d06..bc16556 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -161,8 +161,11 @@ public final class QrCode { * @return the module's color, which is either false (white) or true (black) */ public boolean getModule(int x, int y) { - return 0 <= x && x < size && 0 <= y && y < size - && getModuleUnchecked(x, y) != 0; + if (0 <= x && x < size && 0 <= y && y < size) { + int i = y * size + x; + return ((modules[i >>> 5] >>> i) & 1) != 0; + } else + return false; } @@ -273,12 +276,6 @@ public final class QrCode { } - private int getModuleUnchecked(int x, int y) { - int i = y * size + x; - return (modules[i >>> 5] >>> i) & 1; - } - - /*---- Private helper methods for constructor: Codewords and masking ----*/ // Returns a new byte string representing the given data with the appropriate error correction From 63eaacac1b0530f29f8c64c2136ff6b7c2e330ad Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Thu, 30 Nov 2017 05:08:12 +0000 Subject: [PATCH 14/85] Updated URLs to the project-specific new page. --- src/io/nayuki/fastqrcodegen/BitBuffer.java | 2 +- src/io/nayuki/fastqrcodegen/QrCode.java | 2 +- src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java | 2 +- src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java | 2 +- src/io/nayuki/fastqrcodegen/QrSegment.java | 2 +- src/io/nayuki/fastqrcodegen/QrTemplate.java | 2 +- src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java index 875d1f8..d5b046b 100644 --- a/src/io/nayuki/fastqrcodegen/BitBuffer.java +++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java @@ -2,7 +2,7 @@ * Fast QR Code generator library * * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/ + * https://www.nayuki.io/page/fast-qr-code-generator-library * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index bc16556..ac3d381 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -2,7 +2,7 @@ * Fast QR Code generator library * * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/ + * https://www.nayuki.io/page/fast-qr-code-generator-library * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java index 4d4bff5..c3aa2dd 100644 --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java @@ -5,7 +5,7 @@ * PNG and SVG files in the current working directory to demonstrate the creation of QR Codes. * * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/ + * https://www.nayuki.io/page/fast-qr-code-generator-library * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java index cd04eb5..eb9dd36 100644 --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java @@ -7,7 +7,7 @@ * batch testing of end-to-end functionality of this QR Code generator library. * * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/ + * https://www.nayuki.io/page/fast-qr-code-generator-library * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java index d897a2c..c0cc5b3 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegment.java +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -2,7 +2,7 @@ * Fast QR Code generator library * * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/ + * https://www.nayuki.io/page/fast-qr-code-generator-library * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index b31da45..37d292b 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -2,7 +2,7 @@ * Fast QR Code generator library * * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/ + * https://www.nayuki.io/page/fast-qr-code-generator-library * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java index 3ad23f1..ff76842 100644 --- a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -2,7 +2,7 @@ * Fast QR Code generator library * * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/ + * https://www.nayuki.io/page/fast-qr-code-generator-library * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in From 52ae1e387f9d0c016fbfc399719e056e80b1ce38 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 24 Jun 2018 23:41:32 +0000 Subject: [PATCH 15/85] Added integer overflow checks to toImage() and toSvgString(). --- src/io/nayuki/fastqrcodegen/QrCode.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index ac3d381..3a6e2e0 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -182,6 +182,9 @@ public final class QrCode { public BufferedImage toImage(int scale, int border) { if (scale <= 0 || border < 0) throw new IllegalArgumentException("Value out of range"); + if (border > Integer.MAX_VALUE / 2 || size + border * 2L > Integer.MAX_VALUE / scale) + throw new IllegalArgumentException("Scale or border too large"); + BufferedImage result = new BufferedImage((size + border * 2) * scale, (size + border * 2) * scale, BufferedImage.TYPE_INT_RGB); for (int y = 0; y < result.getHeight(); y++) { for (int x = 0; x < result.getWidth(); x++) { @@ -203,6 +206,9 @@ public final class QrCode { public String toSvgString(int border) { if (border < 0) throw new IllegalArgumentException("Border must be non-negative"); + if (size + border * 2L > Integer.MAX_VALUE) + throw new IllegalArgumentException("Border too large"); + StringBuilder sb = new StringBuilder(); sb.append("\n"); sb.append("\n"); From b204202684272d02a1fd6ad725ccbd6b0f40c9e2 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 24 Jun 2018 23:41:53 +0000 Subject: [PATCH 16/85] Tweaked pluralization in section comments. --- src/io/nayuki/fastqrcodegen/BitBuffer.java | 2 +- src/io/nayuki/fastqrcodegen/QrCode.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java index d5b046b..630ad5b 100644 --- a/src/io/nayuki/fastqrcodegen/BitBuffer.java +++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java @@ -37,7 +37,7 @@ final class BitBuffer { - /*---- Constructors ----*/ + /*---- Constructor ----*/ public BitBuffer() { data = new int[64]; diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 3a6e2e0..8fe6a3f 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -125,7 +125,7 @@ public final class QrCode { - /*---- Constructors ----*/ + /*---- Constructor ----*/ public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) { // Check arguments From 9071594f6c0058bb544c11ae638f155f3d25eb05 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 24 Jun 2018 23:42:17 +0000 Subject: [PATCH 17/85] Tweaked comment for kanji demo so that it corresponds to the order in the sample text string. --- src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java index c3aa2dd..d9b1c21 100644 --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java @@ -132,7 +132,7 @@ public final class QrCodeGeneratorDemo { qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); writePng(qr.toImage(8, 5), "phi-segmented-QR.png"); - // Illustration "Madoka": kanji, kana, Greek, Cyrillic, full-width Latin characters + // Illustration "Madoka": kanji, kana, Cyrillic, full-width Latin, Greek characters String madoka = "「魔法少女まどか☆マギカ」って、 ИАИ desu κα?"; qr = QrCode.encodeText(madoka, QrCode.Ecc.LOW); writePng(qr.toImage(9, 4), "madoka-utf8-QR.png"); From 032b47d4e6128b824cf474587715edd72cbb55ab Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 24 Jun 2018 23:44:16 +0000 Subject: [PATCH 18/85] Added readme document. --- Readme.markdown | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Readme.markdown diff --git a/Readme.markdown b/Readme.markdown new file mode 100644 index 0000000..9e7380f --- /dev/null +++ b/Readme.markdown @@ -0,0 +1,79 @@ +Fast QR Code generator library +============================== + + +Introduction +------------ + +This Java library generates QR Code symbols, and its design is optimized for speed. It contrasts with another QR library by the same author which is slow but which optimizes for clarity and conciseness. The functionality of this library and its API are nearly identical to the slow library, but it runs anywhere from 1.5× to 6× as fast. + +Home page for the fast library (design explanation, benchmarks): https://www.nayuki.io/page/fast-qr-code-generator-library + +Home page for the slow library (live demo, QR Code introduction, competitor comparisons): [https://www.nayuki.io/page/qr-code-generator-library](https://www.nayuki.io/page/qr-code-generator-library) + + +Features +-------- + +Core features: + +* Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard +* Output formats: Raw modules/pixels of the QR symbol, SVG XML string, `BufferedImage` raster bitmap +* Encodes numeric and special-alphanumeric text in less space than general text +* Open source code under the permissive MIT License + +Manual parameters: + +* User can specify minimum and maximum version numbers allowed, then library will automatically choose smallest version in the range that fits the data +* User can specify mask pattern manually, otherwise library will automatically evaluate all 8 masks and select the optimal one +* User can specify absolute error correction level, or allow the library to boost it if it doesn't increase the version number +* User can create a list of data segments manually and add ECI segments + + +Examples +-------- + + import java.awt.image.BufferedImage; + import java.io.File; + import java.util.List; + import javax.imageio.ImageIO; + import io.nayuki.fastqrcodegen.*; + + // Simple operation + QrCode qr0 = QrCode.encodeText("Hello, world!", QrCode.Ecc.MEDIUM); + BufferedImage img = qr0.toImage(4, 10); + ImageIO.write(img, "png", new File("qr-code.png")); + + // Manual operation + List segs = QrSegment.makeSegments("3141592653589793238462643383"); + QrCode qr1 = QrCode.encodeSegments(segs, QrCode.Ecc.HIGH, 5, 5, 2, false); + for (int y = 0; y < qr1.size; y++) { + for (int x = 0; x < qr1.size; x++) { + (... paint qr1.getModule(x, y) ...) + } + } + + +License +------- + +Copyright © 2018 Project Nayuki. (MIT License) +[https://www.nayuki.io/page/fast-qr-code-generator-library](https://www.nayuki.io/page/fast-qr-code-generator-library) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +* The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + +* The Software is provided "as is", without warranty of any kind, express or + implied, including but not limited to the warranties of merchantability, + fitness for a particular purpose and noninfringement. In no event shall the + authors or copyright holders be liable for any claim, damages or other + liability, whether in an action of contract, tort or otherwise, arising from, + out of or in connection with the Software or the use or other dealings in the + Software. From b3fc497299f6e67141776a6c0aa3474e7b4d2b50 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 25 Aug 2018 23:20:58 +0000 Subject: [PATCH 19/85] Simplified loop ranges without changing visible behavior. --- src/io/nayuki/fastqrcodegen/QrCode.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 8fe6a3f..f611a13 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -218,8 +218,8 @@ public final class QrCode { sb.append("\t\n"); sb.append("\t Date: Sat, 25 Aug 2018 23:23:26 +0000 Subject: [PATCH 20/85] Simplified code to use StringBuilder method chaining. --- src/io/nayuki/fastqrcodegen/QrCode.java | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index f611a13..7124f6a 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -209,14 +209,13 @@ public final class QrCode { if (size + border * 2L > Integer.MAX_VALUE) throw new IllegalArgumentException("Border too large"); - StringBuilder sb = new StringBuilder(); - sb.append("\n"); - sb.append("\n"); - sb.append(String.format( - "\n", - size + border * 2)); - sb.append("\t\n"); - sb.append("\t\n") + .append("\n") + .append(String.format("\n", + size + border * 2)) + .append("\t\n") + .append("\t\n"); - sb.append("\n"); - return sb.toString(); + return sb + .append("\" fill=\"#000000\"/>\n") + .append("\n") + .toString(); } From 7fa8becaf72df247d0f2b2e849141ddae0f5dfd6 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 25 Aug 2018 23:24:16 +0000 Subject: [PATCH 21/85] Tweaked code to avoid overflow entirely. --- src/io/nayuki/fastqrcodegen/QrCode.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 7124f6a..dfe4448 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -206,14 +206,12 @@ public final class QrCode { public String toSvgString(int border) { if (border < 0) throw new IllegalArgumentException("Border must be non-negative"); - if (size + border * 2L > Integer.MAX_VALUE) - throw new IllegalArgumentException("Border too large"); - + long brd = border; StringBuilder sb = new StringBuilder() .append("\n") .append("\n") .append(String.format("\n", - size + border * 2)) + size + brd * 2)) .append("\t\n") .append("\t Date: Sat, 25 Aug 2018 23:46:06 +0000 Subject: [PATCH 22/85] Simplified some code, without changing behavior. --- src/io/nayuki/fastqrcodegen/QrCode.java | 3 +-- src/io/nayuki/fastqrcodegen/QrSegment.java | 6 ++---- src/io/nayuki/fastqrcodegen/QrTemplate.java | 9 ++------- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index dfe4448..95242d5 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -129,7 +129,7 @@ public final class QrCode { public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) { // Check arguments - Objects.requireNonNull(ecl); + errorCorrectionLevel = Objects.requireNonNull(ecl); if (ver < MIN_VERSION || ver > MAX_VERSION || mask < -1 || mask > 7) throw new IllegalArgumentException("Value out of range"); Objects.requireNonNull(dataCodewords); @@ -137,7 +137,6 @@ public final class QrCode { // Initialize fields version = ver; size = ver * 4 + 17; - errorCorrectionLevel = ecl; QrTemplate tpl = QrTemplate.getInstance(ver); modules = tpl.template.clone(); diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java index c0cc5b3..ea48ce1 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegment.java +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -156,13 +156,11 @@ public final class QrSegment { /*---- Constructor ----*/ public QrSegment(Mode md, int numCh, int[] data, int bitLen) { - Objects.requireNonNull(md); - Objects.requireNonNull(data); + mode = Objects.requireNonNull(md); + this.data = Objects.requireNonNull(data); if (numCh < 0 || bitLen < 0 || bitLen > data.length * 32) throw new IllegalArgumentException("Invalid value"); - mode = md; numChars = numCh; - this.data = data; bitLength = bitLen; } diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 37d292b..61acfa6 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -285,13 +285,8 @@ final class QrTemplate { return new int[]{}; else { int numAlign = ver / 7 + 2; - int step; - if (ver != 32) { - // ceil((size - 13) / (2*numAlign - 2)) * 2 - step = (ver * 4 + numAlign * 2 + 1) / (2 * numAlign - 2) * 2; - } else // C-C-C-Combo breaker! - step = 26; - + int step = (ver == 32) ? 26 : + (ver*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2; int[] result = new int[numAlign]; result[0] = 6; for (int i = result.length - 1, pos = ver * 4 + 10; i >= 1; i--, pos -= step) From 887b6255ed374a57f26be650515577a2906b96e2 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 25 Aug 2018 23:47:47 +0000 Subject: [PATCH 23/85] Updated and simplified Reed-Solomon ECC computation to reduce temporary buffers and copying. --- src/io/nayuki/fastqrcodegen/QrCode.java | 13 +++++------- .../fastqrcodegen/ReedSolomonGenerator.java | 20 +++++++++---------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 95242d5..4ceb507 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -295,16 +295,13 @@ public final class QrCode { int shortBlockLen = rawCodewords / numBlocks; // Split data into blocks and append ECC to each block - byte[][] blocks = new byte[numBlocks][]; + byte[][] blocks = new byte[numBlocks][shortBlockLen + 1]; ReedSolomonGenerator rs = ReedSolomonGenerator.getInstance(blockEccLen); - byte[] ecc = new byte[blockEccLen]; for (int i = 0, k = 0; i < numBlocks; i++) { - byte[] dat = Arrays.copyOfRange(data, k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1)); - byte[] block = Arrays.copyOf(dat, shortBlockLen + 1); - k += dat.length; - rs.getRemainder(dat, ecc); - System.arraycopy(ecc, 0, block, block.length - blockEccLen, ecc.length); - blocks[i] = block; + int datLen = shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1); + System.arraycopy(data, k, blocks[i], 0, datLen); + rs.getRemainder(data, k, datLen, blocks[i], shortBlockLen + 1 - blockEccLen); + k += datLen; } // Interleave (not concatenate) the bytes from every block into a single sequence diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java index ff76842..7662b65 100644 --- a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -116,20 +116,20 @@ final class ReedSolomonGenerator { } - public void getRemainder(byte[] data, byte[] result) { + public void getRemainder(byte[] data, int dataOff, int dataLen, byte[] result, int resultOff) { Objects.requireNonNull(data); Objects.requireNonNull(result); - if (result.length != multiplies.length) - throw new IllegalArgumentException("Array length mismatch"); // Compute the remainder by performing polynomial division - Arrays.fill(result, (byte)0); - 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] ^= multiplies[i][factor]; + int resultEnd = resultOff + multiplies.length; + Arrays.fill(result, resultOff, resultEnd, (byte)0); + for (int i = dataOff, dataEnd = dataOff + dataLen; i < dataEnd; i++) { + byte b = data[i]; + int factor = (b ^ result[resultOff]) & 0xFF; + System.arraycopy(result, resultOff + 1, result, resultOff, multiplies.length - 1); + result[resultEnd - 1] = 0; + for (int j = 0; j < multiplies.length; j++) + result[resultOff + j] ^= multiplies[j][factor]; } } From 0e5e3c1b6125ffadfa3e05f2070773ce00ae7683 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 25 Aug 2018 23:52:36 +0000 Subject: [PATCH 24/85] Tweaked code to convert most explicit assertion checks to native assert statements. --- src/io/nayuki/fastqrcodegen/QrCode.java | 12 ++++-------- src/io/nayuki/fastqrcodegen/QrTemplate.java | 6 ++---- .../nayuki/fastqrcodegen/ReedSolomonGenerator.java | 6 ++---- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 4ceb507..cdb6bab 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -70,8 +70,7 @@ public final class QrCode { if (version >= maxVersion) // All versions in the range could not fit the given data throw new IllegalArgumentException("Data too long"); } - if (dataUsedBits == -1) - throw new AssertionError(); + assert dataUsedBits != -1; // Increase the error correction level while the data still fits in the current version number for (Ecc newEcl : Ecc.values()) { @@ -95,8 +94,7 @@ public final class QrCode { // Pad with alternate bytes until data capacity is reached for (int padByte = 0xEC; bb.bitLength < dataCapacityBits; padByte ^= 0xEC ^ 0x11) bb.appendBits(padByte, 8); - if (bb.bitLength % 8 != 0) - throw new AssertionError(); + assert bb.bitLength % 8 == 0; // Create the QR Code symbol return new QrCode(version, ecl, bb.getBytes(), mask); @@ -245,8 +243,7 @@ public final class QrCode { rem = (rem << 1) ^ ((rem >>> 9) * 0x537); data = data << 10 | rem; data ^= 0x5412; // uint15 - if (data >>> 15 != 0) - throw new AssertionError(); + assert data >>> 15 == 0; // Draw first copy for (int i = 0; i <= 5; i++) @@ -363,8 +360,7 @@ public final class QrCode { applyMask(masks[i]); // Undoes the mask due to XOR } } - if (mask < 0 || mask > 7) - throw new AssertionError(); + assert 0 <= mask && mask <= 7; drawFormatBits(mask); // Overwrite old format bits applyMask(masks[mask]); // Apply the final choice of mask return mask; // The caller shall assign this value to the final-declared field diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 61acfa6..4353e40 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -169,8 +169,7 @@ final class QrTemplate { for (int i = 0; i < 12; i++) rem = (rem << 1) ^ ((rem >>> 11) * 0x1F25); int data = version << 12 | rem; // uint18 - if (data >>> 18 != 0) - throw new AssertionError(); + assert data >>> 18 == 0; // Draw two copies for (int i = 0; i < 18; i++) { @@ -249,8 +248,7 @@ final class QrTemplate { } } } - if (i != result.length) - throw new AssertionError(); + assert i == result.length; return result; } diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java index 7662b65..ae17275 100644 --- a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -140,16 +140,14 @@ final class ReedSolomonGenerator { // 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) { - if (x >>> 8 != 0 || y >>> 8 != 0) - throw new IllegalArgumentException("Byte out of range"); + 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; } - if (z >>> 8 != 0) - throw new AssertionError(); + assert z >>> 8 == 0; return z; } From b7f8d3a239663233b154ea690545bb23c71570ad Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 26 Aug 2018 00:00:32 +0000 Subject: [PATCH 25/85] Tweaked some bit-drawing code for clarity. --- src/io/nayuki/fastqrcodegen/QrCode.java | 19 +++++++++---------- src/io/nayuki/fastqrcodegen/QrTemplate.java | 6 +++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index cdb6bab..6fed91e 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -241,24 +241,23 @@ public final class QrCode { int rem = data; for (int i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537); - data = data << 10 | rem; - data ^= 0x5412; // uint15 - assert data >>> 15 == 0; + int bits = (data << 10 | rem) ^ 0x5412; // uint15 + assert bits >>> 15 == 0; // Draw first copy for (int i = 0; i <= 5; i++) - setModule(8, i, (data >>> i) & 1); - setModule(8, 7, (data >>> 6) & 1); - setModule(8, 8, (data >>> 7) & 1); - setModule(7, 8, (data >>> 8) & 1); + setModule(8, i, (bits >>> i) & 1); + setModule(8, 7, (bits >>> 6) & 1); + setModule(8, 8, (bits >>> 7) & 1); + setModule(7, 8, (bits >>> 8) & 1); for (int i = 9; i < 15; i++) - setModule(14 - i, 8, (data >>> i) & 1); + setModule(14 - i, 8, (bits >>> i) & 1); // Draw second copy for (int i = 0; i <= 7; i++) - setModule(size - 1 - i, 8, (data >>> i) & 1); + setModule(size - 1 - i, 8, (bits >>> i) & 1); for (int i = 8; i < 15; i++) - setModule(8, size - 15 + i, (data >>> i) & 1); + setModule(8, size - 15 + i, (bits >>> i) & 1); setModule(8, size - 8, 1); } diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 4353e40..cbc50ff 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -168,12 +168,12 @@ final class QrTemplate { int rem = version; // version is uint6, in the range [7, 40] for (int i = 0; i < 12; i++) rem = (rem << 1) ^ ((rem >>> 11) * 0x1F25); - int data = version << 12 | rem; // uint18 - assert data >>> 18 == 0; + int bits = version << 12 | rem; // uint18 + assert bits >>> 18 == 0; // Draw two copies for (int i = 0; i < 18; i++) { - int bit = (data >>> i) & 1; + int bit = (bits >>> i) & 1; int a = size - 11 + i % 3, b = i / 3; darkenFunctionModule(a, b, bit); darkenFunctionModule(b, a, bit); From 593ff051bf6606d5075ec61ba92db4737287575c Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 26 Aug 2018 00:01:00 +0000 Subject: [PATCH 26/85] Clarified and simplified some code. --- src/io/nayuki/fastqrcodegen/QrCode.java | 4 ++-- src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java | 11 +++-------- src/io/nayuki/fastqrcodegen/QrTemplate.java | 3 ++- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 6fed91e..a4d623f 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -185,8 +185,8 @@ public final class QrCode { BufferedImage result = new BufferedImage((size + border * 2) * scale, (size + border * 2) * scale, BufferedImage.TYPE_INT_RGB); for (int y = 0; y < result.getHeight(); y++) { for (int x = 0; x < result.getWidth(); x++) { - boolean val = getModule(x / scale - border, y / scale - border); - result.setRGB(x, y, val ? 0x000000 : 0xFFFFFF); + boolean color = getModule(x / scale - border, y / scale - border); + result.setRGB(x, y, color ? 0x000000 : 0xFFFFFF); } } return result; diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java index d9b1c21..03920e2 100644 --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java @@ -28,11 +28,9 @@ package io.nayuki.fastqrcodegen; import java.awt.image.BufferedImage; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.Arrays; import java.util.List; import javax.imageio.ImageIO; @@ -64,11 +62,8 @@ public final class QrCodeGeneratorDemo { ImageIO.write(img, "png", imgFile); // Write image to file String svg = qr.toSvgString(4); // Convert to SVG XML code - try (Writer out = new OutputStreamWriter( - new FileOutputStream("hello-world-QR.svg"), - StandardCharsets.UTF_8)) { - out.write(svg); // Create/overwrite file and write SVG data - } + Files.write(new File("hello-world-QR.svg").toPath(), + svg.getBytes(StandardCharsets.UTF_8)); } diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index cbc50ff..97a4fbd 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -174,7 +174,8 @@ final class QrTemplate { // Draw two copies for (int i = 0; i < 18; i++) { int bit = (bits >>> i) & 1; - int a = size - 11 + i % 3, b = i / 3; + int a = size - 11 + i % 3; + int b = i / 3; darkenFunctionModule(a, b, bit); darkenFunctionModule(b, a, bit); } From 3e2770d6c01b40d5c72e5308779df39e2476514c Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 26 Aug 2018 03:53:36 +0000 Subject: [PATCH 27/85] Somewhat simplified black/white balance penalty calculation. --- src/io/nayuki/fastqrcodegen/QrCode.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index a4d623f..797d59e 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -436,10 +436,10 @@ public final class QrCode { } // Balance of black and white modules - int total = size * size; - // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% - for (int k = 0; black*20 < (9-k)*total || black*20 > (11+k)*total; k++) - result += PENALTY_N4; + int total = size * size; // Note that size is odd, so black/total != 1/2 + // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)% + int k = (Math.abs(black * 20 - total * 10) + total - 1) / total - 1; + result += k * PENALTY_N4; return result; } From 567dbbb0670ee5f924329d5e162056300e9893a4 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 28 Aug 2018 04:17:28 +0000 Subject: [PATCH 28/85] De-optimized Reed-Solomon generator to not store one step of precomputing multiplication tables, in preparation for next change. --- .../fastqrcodegen/ReedSolomonGenerator.java | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java index ae17275..979faec 100644 --- a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -110,9 +110,11 @@ final class ReedSolomonGenerator { root = multiply(root, 0x02); } - multiplies = new byte[degree][]; - for (int i = 0; i < multiplies.length; i++) - multiplies[i] = MULTIPLICATION_TABLE[coefficients[i] & 0xFF]; + multiplies = new byte[degree][256]; + for (int i = 0; i < multiplies.length; i++) { + for (int j = 0; j < 256; j++) + multiplies[i][j] = (byte)multiply(coefficients[i] & 0xFF, j); + } } @@ -151,17 +153,4 @@ final class ReedSolomonGenerator { return z; } - - private static final byte[][] MULTIPLICATION_TABLE = new byte[256][256]; - - static { - for (int i = 0; i < MULTIPLICATION_TABLE.length; i++) { - for (int j = 0; j <= i; j++) { - byte k = (byte)multiply(i, j); - MULTIPLICATION_TABLE[i][j] = k; - MULTIPLICATION_TABLE[j][i] = k; - } - } - } - } From ccd7f3e9e89a83b6c4ad5c3067f5f50fb6a91f4f Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 28 Aug 2018 04:25:48 +0000 Subject: [PATCH 29/85] Simplified Reed-Solomon generator algorithms, without changing behavior. --- .../fastqrcodegen/ReedSolomonGenerator.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java index 979faec..99ba41b 100644 --- a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -85,7 +85,11 @@ final class ReedSolomonGenerator { /*---- Instance members ----*/ - private byte[][] multiplies; + // 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, + // 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 ReedSolomonGenerator(int degree) { @@ -110,10 +114,10 @@ final class ReedSolomonGenerator { root = multiply(root, 0x02); } - multiplies = new byte[degree][256]; - for (int i = 0; i < multiplies.length; i++) { - for (int j = 0; j < 256; j++) - multiplies[i][j] = (byte)multiply(coefficients[i] & 0xFF, j); + polynomialMultiply = new byte[256][degree]; + for (int i = 0; i < polynomialMultiply.length; i++) { + for (int j = 0; j < degree; j++) + polynomialMultiply[i][j] = (byte)multiply(i, coefficients[j] & 0xFF); } } @@ -123,15 +127,14 @@ final class ReedSolomonGenerator { Objects.requireNonNull(result); // Compute the remainder by performing polynomial division - int resultEnd = resultOff + multiplies.length; + int degree = polynomialMultiply[0].length; + int resultEnd = resultOff + degree; Arrays.fill(result, resultOff, resultEnd, (byte)0); for (int i = dataOff, dataEnd = dataOff + dataLen; i < dataEnd; i++) { - byte b = data[i]; - int factor = (b ^ result[resultOff]) & 0xFF; - System.arraycopy(result, resultOff + 1, result, resultOff, multiplies.length - 1); - result[resultEnd - 1] = 0; - for (int j = 0; j < multiplies.length; j++) - result[resultOff + j] ^= multiplies[j][factor]; + byte[] table = polynomialMultiply[(data[i] ^ result[resultOff]) & 0xFF]; + for (int j = 0; j < degree - 1; j++) + result[resultOff + j] = (byte)(result[resultOff + j + 1] ^ table[j]); + result[resultOff + degree - 1] = table[degree - 1]; } } From f8e59274f6b91e97f8320aa0827d57d4ebf9b592 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 28 Aug 2018 06:54:54 +0000 Subject: [PATCH 30/85] Simplified and renamed addEccAndInterleave(), based on the slow QR library C version algorithm. --- src/io/nayuki/fastqrcodegen/QrCode.java | 35 +++++++++++-------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 797d59e..3390eb2 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -140,7 +140,7 @@ public final class QrCode { modules = tpl.template.clone(); // Draw function patterns, draw all codewords, do masking - byte[] allCodewords = appendErrorCorrection(dataCodewords); + byte[] allCodewords = addEccAndInterleave(dataCodewords); drawCodewords(tpl.dataOutputBitIndexes, allCodewords); this.mask = handleConstructorMasking(tpl.masks, mask); } @@ -279,7 +279,7 @@ public final class QrCode { // Returns a new byte string representing the given data with the appropriate error correction // codewords appended to it, based on this object's version and error correction level. - private byte[] appendErrorCorrection(byte[] data) { + private byte[] addEccAndInterleave(byte[] data) { if (data.length != getNumDataCodewords(version, errorCorrectionLevel)) throw new IllegalArgumentException(); @@ -288,28 +288,23 @@ public final class QrCode { int blockEccLen = ECC_CODEWORDS_PER_BLOCK[errorCorrectionLevel.ordinal()][version]; int rawCodewords = QrTemplate.getNumRawDataModules(version) / 8; int numShortBlocks = numBlocks - rawCodewords % numBlocks; - int shortBlockLen = rawCodewords / numBlocks; + int shortBlockDataLen = rawCodewords / numBlocks - blockEccLen; - // Split data into blocks and append ECC to each block - byte[][] blocks = new byte[numBlocks][shortBlockLen + 1]; + // Split data into blocks, calculate ECC, and interleave + // (not concatenate) the bytes into a single sequence + byte[] result = new byte[rawCodewords]; ReedSolomonGenerator rs = ReedSolomonGenerator.getInstance(blockEccLen); + byte[] ecc = new byte[blockEccLen]; // Temporary storage per iteration for (int i = 0, k = 0; i < numBlocks; i++) { - int datLen = shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1); - System.arraycopy(data, k, blocks[i], 0, datLen); - rs.getRemainder(data, k, datLen, blocks[i], shortBlockLen + 1 - blockEccLen); - k += datLen; - } - - // Interleave (not concatenate) the bytes from every block into a single sequence - byte[] result = new byte[rawCodewords]; - for (int i = 0, k = 0; i < blocks[0].length; i++) { - for (int j = 0; j < blocks.length; j++) { - // Skip the padding byte in short blocks - if (i != shortBlockLen - blockEccLen || j >= numShortBlocks) { - result[k] = blocks[j][i]; - k++; - } + int datLen = shortBlockDataLen + (i < numShortBlocks ? 0 : 1); + rs.getRemainder(data, k, datLen, ecc, 0); + for (int j = 0, l = i; j < datLen; j++, k++, l += numBlocks) { // Copy data + if (j == shortBlockDataLen) + l -= numShortBlocks; + result[l] = data[k]; } + for (int j = 0, l = data.length + i; j < blockEccLen; j++, l += numBlocks) // Copy ECC + result[l] = ecc[j]; } return result; } From 943b8815ee8ffb03dc03e034da40e709e9b4a756 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 28 Aug 2018 06:59:00 +0000 Subject: [PATCH 31/85] Deleted unused parameter and simplified code in ReedSolomonGenerator.getRemainder(). --- src/io/nayuki/fastqrcodegen/QrCode.java | 2 +- .../nayuki/fastqrcodegen/ReedSolomonGenerator.java | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 3390eb2..61f37c8 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -297,7 +297,7 @@ public final class QrCode { byte[] ecc = new byte[blockEccLen]; // Temporary storage per iteration for (int i = 0, k = 0; i < numBlocks; i++) { int datLen = shortBlockDataLen + (i < numShortBlocks ? 0 : 1); - rs.getRemainder(data, k, datLen, ecc, 0); + rs.getRemainder(data, k, datLen, ecc); for (int j = 0, l = i; j < datLen; j++, k++, l += numBlocks) { // Copy data if (j == shortBlockDataLen) l -= numShortBlocks; diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java index 99ba41b..aef45c0 100644 --- a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -122,19 +122,19 @@ final class ReedSolomonGenerator { } - public void getRemainder(byte[] data, int dataOff, int dataLen, byte[] result, int resultOff) { + public void getRemainder(byte[] data, int dataOff, int dataLen, byte[] result) { Objects.requireNonNull(data); Objects.requireNonNull(result); + int degree = polynomialMultiply[0].length; + assert result.length == degree; // Compute the remainder by performing polynomial division - int degree = polynomialMultiply[0].length; - int resultEnd = resultOff + degree; - Arrays.fill(result, resultOff, resultEnd, (byte)0); + Arrays.fill(result, (byte)0); for (int i = dataOff, dataEnd = dataOff + dataLen; i < dataEnd; i++) { - byte[] table = polynomialMultiply[(data[i] ^ result[resultOff]) & 0xFF]; + byte[] table = polynomialMultiply[(data[i] ^ result[0]) & 0xFF]; for (int j = 0; j < degree - 1; j++) - result[resultOff + j] = (byte)(result[resultOff + j + 1] ^ table[j]); - result[resultOff + degree - 1] = table[degree - 1]; + result[j] = (byte)(result[j + 1] ^ table[j]); + result[degree - 1] = table[degree - 1]; } } From 518850d81a749a68927951cc4be57b4b9dfb3eba Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 28 Aug 2018 07:17:45 +0000 Subject: [PATCH 32/85] Updated various comments - Javadoc, method-level, intra-method. --- src/io/nayuki/fastqrcodegen/QrCode.java | 19 +++++++++++-------- src/io/nayuki/fastqrcodegen/QrTemplate.java | 10 ++++++---- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 61f37c8..7c00e75 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -73,12 +73,12 @@ public final class QrCode { assert dataUsedBits != -1; // Increase the error correction level while the data still fits in the current version number - for (Ecc newEcl : Ecc.values()) { + for (Ecc newEcl : Ecc.values()) { // From low to high if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) ecl = newEcl; } - // Create the data bit string by concatenating all segments + // Concatenate all segments to create the data bit string int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; BitBuffer bb = new BitBuffer(); for (QrSegment seg : segs) { @@ -91,7 +91,7 @@ public final class QrCode { bb.appendBits(0, Math.min(4, dataCapacityBits - bb.bitLength)); bb.appendBits(0, (8 - bb.bitLength % 8) % 8); - // Pad with alternate bytes until data capacity is reached + // Pad with alternating bytes until data capacity is reached for (int padByte = 0xEC; bb.bitLength < dataCapacityBits; padByte ^= 0xEC ^ 0x11) bb.appendBits(padByte, 8); assert bb.bitLength % 8 == 0; @@ -174,7 +174,8 @@ public final class QrCode { * @param scale the module scale factor, which must be positive * @param border the number of border modules to add, which must be non-negative * @return an image representing this QR Code, with padding and scaling - * @throws IllegalArgumentException if the scale or border is out of range + * @throws IllegalArgumentException if the scale or border is out of range, or if + * {scale, border, size} cause the image dimensions to exceed Integer.MAX_VALUE */ public BufferedImage toImage(int scale, int border) { if (scale <= 0 || border < 0) @@ -199,6 +200,7 @@ public final class QrCode { * Note that Unix newlines (\n) are always used, regardless of the platform. * @param border the number of border modules to add, which must be non-negative * @return a string representing this QR Code as an SVG document + * @throws IllegalArgumentException if the border is negative */ public String toSvgString(int border) { if (border < 0) @@ -325,10 +327,11 @@ public final class QrCode { } - // XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical - // properties, calling applyMask(m) twice with the same value is equivalent to no change at all. - // This means it is possible to apply a mask, undo it, and try another mask. Note that a final - // well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). + // XORs the codeword modules in this QR Code with the given mask pattern. + // The function modules must be marked and the codeword bits must be drawn + // 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 + // QR Code symbol needs exactly one (not zero, two, etc.) mask applied. private void applyMask(int[] mask) { if (mask.length != modules.length) throw new IllegalArgumentException(); diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 97a4fbd..6d4024e 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -159,7 +159,7 @@ final class QrTemplate { // Draws two copies of the version bits (with its own error correction code), - // based on this object's version field (which only has an effect for 7 <= version <= 40). + // based on this object's version field, iff 7 <= version <= 40. private void drawVersion() { if (version < 7) return; @@ -182,7 +182,8 @@ final class QrTemplate { } - // Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). + // Draws a 9*9 finder pattern including the border separator, + // with the center module at (x, y). Modules can be out of bounds. private void drawFinderPattern(int x, int y) { for (int i = -4; i <= 4; i++) { for (int j = -4; j <= 4; j++) { @@ -195,7 +196,8 @@ final class QrTemplate { } - // Draws a 5*5 alignment pattern, with the center module at (x, y). + // Draws a 5*5 alignment pattern, with the center module + // at (x, y). All modules must be in bounds. private void drawAlignmentPattern(int x, int y) { for (int i = -2; i <= 2; i++) { for (int j = -2; j <= 2; j++) @@ -306,7 +308,7 @@ final class QrTemplate { int numAlign = ver / 7 + 2; result -= (25 * numAlign - 10) * numAlign - 55; if (ver >= 7) - result -= 18 * 2; // Subtract version information + result -= 18 * 2; } return result; } From a268c93ec5a722ada9d92ec847e8d727f9fcf762 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 28 Aug 2018 07:18:27 +0000 Subject: [PATCH 33/85] Clarified some assertions and code. --- src/io/nayuki/fastqrcodegen/QrCode.java | 6 ++++-- src/io/nayuki/fastqrcodegen/QrTemplate.java | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 7c00e75..c54d718 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -79,22 +79,24 @@ public final class QrCode { } // Concatenate all segments to create the data bit string - int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; BitBuffer bb = new BitBuffer(); for (QrSegment seg : segs) { bb.appendBits(seg.mode.modeBits, 4); bb.appendBits(seg.numChars, seg.mode.numCharCountBits(version)); bb.appendBits(seg.data, seg.bitLength); } + assert bb.bitLength == dataUsedBits; // Add terminator and pad up to a byte if applicable + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; + assert bb.bitLength <= dataCapacityBits; bb.appendBits(0, Math.min(4, dataCapacityBits - bb.bitLength)); bb.appendBits(0, (8 - bb.bitLength % 8) % 8); + assert bb.bitLength % 8 == 0; // Pad with alternating bytes until data capacity is reached for (int padByte = 0xEC; bb.bitLength < dataCapacityBits; padByte ^= 0xEC ^ 0x11) bb.appendBits(padByte, 8); - assert bb.bitLength % 8 == 0; // Create the QR Code symbol return new QrCode(version, ecl, bb.getBytes(), mask); diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 6d4024e..4087df4 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -308,7 +308,7 @@ final class QrTemplate { int numAlign = ver / 7 + 2; result -= (25 * numAlign - 10) * numAlign - 55; if (ver >= 7) - result -= 18 * 2; + result -= 36; } return result; } From b3949f6fc45a71d300506028aac21dcdbc5bf91c Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 28 Aug 2018 21:35:31 +0000 Subject: [PATCH 34/85] Changed QrTemplate.getAlignmentPatternPositions() from static function to instance method, and updated comment. --- src/io/nayuki/fastqrcodegen/QrTemplate.java | 22 ++++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 4087df4..41306d1 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -121,7 +121,7 @@ final class QrTemplate { drawFinderPattern(3, size - 4); // Draw numerous alignment patterns - int[] alignPatPos = getAlignmentPatternPositions(version); + int[] alignPatPos = getAlignmentPatternPositions(); int numAlign = alignPatPos.length; for (int i = 0; i < numAlign; i++) { for (int j = 0; j < numAlign; j++) { @@ -276,21 +276,19 @@ final class QrTemplate { /*---- Private static helper functions ----*/ - // Returns a set of positions of the alignment patterns in ascending order. These positions are - // used on both the x and y axes. Each value in the resulting array is in the range [0, 177). - // This stateless pure function could be implemented as table of 40 variable-length lists of unsigned bytes. - private static int[] getAlignmentPatternPositions(int ver) { - if (ver < MIN_VERSION || ver > MAX_VERSION) - throw new IllegalArgumentException("Version number out of range"); - else if (ver == 1) + // Returns an ascending list of positions of alignment patterns for this version number. + // Each position is in the range [0,177), and are used on both the x and y axes. + // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. + private int[] getAlignmentPatternPositions() { + if (version == 1) return new int[]{}; else { - int numAlign = ver / 7 + 2; - int step = (ver == 32) ? 26 : - (ver*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2; + int numAlign = version / 7 + 2; + int step = (version == 32) ? 26 : + (version*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2; int[] result = new int[numAlign]; result[0] = 6; - for (int i = result.length - 1, pos = ver * 4 + 10; i >= 1; i--, pos -= step) + for (int i = result.length - 1, pos = size - 7; i >= 1; i--, pos -= step) result[i] = pos; return result; } From 4cddfddb66202a9f24d6a3ba13f0307644711c60 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 28 Aug 2018 21:44:28 +0000 Subject: [PATCH 35/85] Simplified small pieces of code. --- src/io/nayuki/fastqrcodegen/QrCode.java | 7 +------ src/io/nayuki/fastqrcodegen/QrSegment.java | 8 +------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index c54d718..9935c91 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -215,13 +215,10 @@ public final class QrCode { size + brd * 2)) .append("\t\n") .append("\t MAX_VERSION) - throw new IllegalArgumentException("Version number out of range"); return QrTemplate.getNumRawDataModules(ver) / 8 - ECC_CODEWORDS_PER_BLOCK[ecl.ordinal()][ver] * NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal()][ver]; diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java index ea48ce1..9f363f2 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegment.java +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -168,9 +168,6 @@ public final class QrSegment { // Package-private helper function. static int getTotalBits(List segs, int version) { Objects.requireNonNull(segs); - if (version < 1 || version > 40) - throw new IllegalArgumentException("Version number out of range"); - long result = 0; for (QrSegment seg : segs) { Objects.requireNonNull(seg); @@ -235,10 +232,7 @@ public final class QrSegment { /*-- Method --*/ int numCharCountBits(int ver) { - if ( 1 <= ver && ver <= 9) return numBitsCharCount[0]; - else if (10 <= ver && ver <= 26) return numBitsCharCount[1]; - else if (27 <= ver && ver <= 40) return numBitsCharCount[2]; - else throw new IllegalArgumentException("Version number out of range"); + return numBitsCharCount[(ver + 7) / 17]; } } From 6d5164fa0e9a474e599d0533d084318a0031ae4e Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 28 Aug 2018 21:44:39 +0000 Subject: [PATCH 36/85] Added and updated comments. --- src/io/nayuki/fastqrcodegen/QrCode.java | 7 +++---- src/io/nayuki/fastqrcodegen/QrSegment.java | 9 +++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 9935c91..53550c2 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -197,9 +197,8 @@ public final class QrCode { /** - * Based on the specified number of border modules to add as padding, this returns a - * string whose contents represents an SVG XML file that depicts this QR Code symbol. - * Note that Unix newlines (\n) are always used, regardless of the platform. + * Returns a string of SVG XML code representing an image of this QR Code symbol with the specified + * number of border modules. Note that Unix newlines (\n) are always used, regardless of the platform. * @param border the number of border modules to add, which must be non-negative * @return a string representing this QR Code as an SVG document * @throws IllegalArgumentException if the border is negative @@ -259,7 +258,7 @@ public final class QrCode { setModule(size - 1 - i, 8, (bits >>> i) & 1); for (int i = 8; i < 15; i++) setModule(8, size - 15 + i, (bits >>> i) & 1); - setModule(8, size - 8, 1); + setModule(8, size - 8, 1); // Always black } diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java index 9f363f2..ff31a35 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegment.java +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -165,19 +165,20 @@ public final class QrSegment { } - // Package-private helper function. + // Calculates the number of bits needed to encode the given segments at the given version. + // Returns a non-negative number if successful. Otherwise returns -1 if a segment has too + // many characters to fit its length field, or the total bits exceeds Integer.MAX_VALUE. static int getTotalBits(List segs, int version) { Objects.requireNonNull(segs); long result = 0; for (QrSegment seg : segs) { Objects.requireNonNull(seg); int ccbits = seg.mode.numCharCountBits(version); - // Fail if segment length value doesn't fit in the length field's bit-width if (seg.numChars >= (1 << ccbits)) - return -1; + return -1; // The segment's length doesn't fit the field's bit width result += 4L + ccbits + seg.bitLength; if (result > Integer.MAX_VALUE) - return -1; + return -1; // The sum will overflow an int type } return (int)result; } From 1027ad5b65e07ee8088427ca42c867ebc6071ef0 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Fri, 31 Aug 2018 17:56:33 +0000 Subject: [PATCH 37/85] Reformatted some whitespace for alignment. --- src/io/nayuki/fastqrcodegen/QrCode.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 53550c2..25b57a7 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -285,7 +285,7 @@ public final class QrCode { // Calculate parameter numbers int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[errorCorrectionLevel.ordinal()][version]; - int blockEccLen = ECC_CODEWORDS_PER_BLOCK[errorCorrectionLevel.ordinal()][version]; + int blockEccLen = ECC_CODEWORDS_PER_BLOCK [errorCorrectionLevel.ordinal()][version]; int rawCodewords = QrTemplate.getNumRawDataModules(version) / 8; int numShortBlocks = numBlocks - rawCodewords % numBlocks; int shortBlockDataLen = rawCodewords / numBlocks - blockEccLen; @@ -448,7 +448,7 @@ public final class QrCode { // This stateless pure function could be implemented as a (40*4)-cell lookup table. static int getNumDataCodewords(int ver, Ecc ecl) { return QrTemplate.getNumRawDataModules(ver) / 8 - - ECC_CODEWORDS_PER_BLOCK[ecl.ordinal()][ver] + - ECC_CODEWORDS_PER_BLOCK [ecl.ordinal()][ver] * NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal()][ver]; } @@ -456,8 +456,8 @@ public final class QrCode { /*---- Private tables of constants ----*/ // For use in getPenaltyScore(), when evaluating which mask is best. - private static final int PENALTY_N1 = 3; - private static final int PENALTY_N2 = 3; + private static final int PENALTY_N1 = 3; + private static final int PENALTY_N2 = 3; private static final int PENALTY_N3 = 40; private static final int PENALTY_N4 = 10; From b0a7a4240aa4f423b6506b58aec5c5e142a32c91 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 05:49:13 +0000 Subject: [PATCH 38/85] Added private helper function to reduce occurrences of low-level bit arithmetic. --- src/io/nayuki/fastqrcodegen/QrCode.java | 30 +++++++++++++++---------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 25b57a7..ff0ebaf 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -162,7 +162,7 @@ public final class QrCode { public boolean getModule(int x, int y) { if (0 <= x && x < size && 0 <= y && y < size) { int i = y * size + x; - return ((modules[i >>> 5] >>> i) & 1) != 0; + return getBit(modules[i >>> 5], i) != 0; } else return false; } @@ -246,18 +246,18 @@ public final class QrCode { // Draw first copy for (int i = 0; i <= 5; i++) - setModule(8, i, (bits >>> i) & 1); - setModule(8, 7, (bits >>> 6) & 1); - setModule(8, 8, (bits >>> 7) & 1); - setModule(7, 8, (bits >>> 8) & 1); + setModule(8, i, getBit(bits, i)); + setModule(8, 7, getBit(bits, 6)); + setModule(8, 8, getBit(bits, 7)); + setModule(7, 8, getBit(bits, 8)); for (int i = 9; i < 15; i++) - setModule(14 - i, 8, (bits >>> i) & 1); + setModule(14 - i, 8, getBit(bits, i)); // Draw second copy for (int i = 0; i <= 7; i++) - setModule(size - 1 - i, 8, (bits >>> i) & 1); + setModule(size - 1 - i, 8, getBit(bits, i)); for (int i = 8; i < 15; i++) - setModule(8, size - 15 + i, (bits >>> i) & 1); + setModule(8, size - 15 + i, getBit(bits, i)); setModule(8, size - 8, 1); // Always black } @@ -319,7 +319,7 @@ public final class QrCode { throw new IllegalArgumentException(); for (int i = 0; i < dataOutputBitIndexes.length; i++) { int j = dataOutputBitIndexes[i]; - int bit = (allCodewords[i >>> 3] >>> (~i & 7)) & 1; + int bit = getBit(allCodewords[i >>> 3], ~i & 7); modules[j >>> 5] |= bit << j; } } @@ -377,7 +377,7 @@ public final class QrCode { for (int x = 0; x < size; x++, index++, downIndex++) { // Adjacent modules having same color - int bit = (modules[index >>> 5] >>> index) & 1; + int bit = getBit(modules[index >>> 5], index); if (bit != runColor) { runColor = bit; runLen = 1; @@ -392,7 +392,7 @@ public final class QrCode { black += bit; bits = ((bits & 0b1111111111) << 1) | bit; if (downIndex < end) { - downBits = ((downBits & 1) << 1) | ((modules[downIndex >>> 5] >>> downIndex) & 1); + downBits = ((downBits & 1) << 1) | getBit(modules[downIndex >>> 5], downIndex); // 2*2 blocks of modules having same color if (x >= 1 && (downBits == 0 || downBits == 3) && downBits == (bits & 3)) result += PENALTY_N2; @@ -412,7 +412,7 @@ public final class QrCode { for (int y = 0, index = x; y < size; y++, index += size) { // Adjacent modules having same color - int bit = (modules[index >>> 5] >>> index) & 1; + int bit = getBit(modules[index >>> 5], index); if (bit != runColor) { runColor = bit; runLen = 1; @@ -453,6 +453,12 @@ public final class QrCode { } + // Returns 0 or 1 based on the i'th bit of x. + private static int getBit(int x, int i) { + return (x >>> i) & 1; + } + + /*---- Private tables of constants ----*/ // For use in getPenaltyScore(), when evaluating which mask is best. From 6f79d4c68e42321b603ed9749cd61f0eaaa45a73 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 05:51:10 +0000 Subject: [PATCH 39/85] Tweaked comments and statement order in QrCode constructor. --- src/io/nayuki/fastqrcodegen/QrCode.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index ff0ebaf..bd6e51a 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -128,20 +128,20 @@ public final class QrCode { /*---- Constructor ----*/ public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) { - // Check arguments - errorCorrectionLevel = Objects.requireNonNull(ecl); - if (ver < MIN_VERSION || ver > MAX_VERSION || mask < -1 || mask > 7) - throw new IllegalArgumentException("Value out of range"); - Objects.requireNonNull(dataCodewords); - - // Initialize fields + // Check arguments and initialize fields + if (ver < MIN_VERSION || ver > MAX_VERSION) + throw new IllegalArgumentException("Version value out of range"); + if (mask < -1 || mask > 7) + throw new IllegalArgumentException("Mask value out of range"); version = ver; size = ver * 4 + 17; + errorCorrectionLevel = Objects.requireNonNull(ecl); + Objects.requireNonNull(dataCodewords); QrTemplate tpl = QrTemplate.getInstance(ver); modules = tpl.template.clone(); - // Draw function patterns, draw all codewords, do masking + // Compute ECC, draw modules, do masking byte[] allCodewords = addEccAndInterleave(dataCodewords); drawCodewords(tpl.dataOutputBitIndexes, allCodewords); this.mask = handleConstructorMasking(tpl.masks, mask); From 6a5fdc5687339093e5059c0d1b49e166803511d2 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 05:51:58 +0000 Subject: [PATCH 40/85] Tweaked whitespace for consistency. --- src/io/nayuki/fastqrcodegen/QrCode.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index bd6e51a..b6d5f85 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -139,7 +139,7 @@ public final class QrCode { Objects.requireNonNull(dataCodewords); QrTemplate tpl = QrTemplate.getInstance(ver); - modules = tpl.template.clone(); + modules = tpl.template.clone(); // Compute ECC, draw modules, do masking byte[] allCodewords = addEccAndInterleave(dataCodewords); @@ -241,7 +241,7 @@ public final class QrCode { int rem = data; for (int i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537); - int bits = (data << 10 | rem) ^ 0x5412; // uint15 + int bits = (data << 10 | rem) ^ 0x5412; // uint15 assert bits >>> 15 == 0; // Draw first copy From 820b2ca60a5c31de19373f5c13894d7a418b6297 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 05:54:24 +0000 Subject: [PATCH 41/85] Moved QrCode class's max/min version constants, tweaked associated comments and blank lines. --- src/io/nayuki/fastqrcodegen/QrCode.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index b6d5f85..ea295f4 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -104,13 +104,6 @@ public final class QrCode { - /*---- Public constants ----*/ - - public static final int MIN_VERSION = 1; - public static final int MAX_VERSION = 40; - - - /*---- Instance fields ----*/ public final int version; @@ -459,7 +452,11 @@ public final class QrCode { } - /*---- Private tables of constants ----*/ + /*---- Constants and tables ----*/ + + public static final int MIN_VERSION = 1; + public static final int MAX_VERSION = 40; + // For use in getPenaltyScore(), when evaluating which mask is best. private static final int PENALTY_N1 = 3; From 735994ed2a7dee1774cae19c268d952b3bf74caf Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 05:56:01 +0000 Subject: [PATCH 42/85] Aligned main comments in demo program. --- src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java index 03920e2..10ed286 100644 --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java @@ -57,11 +57,11 @@ public final class QrCodeGeneratorDemo { QrCode qr = QrCode.encodeText(text, errCorLvl); // Make the QR Code symbol - BufferedImage img = qr.toImage(10, 4); // Convert to bitmap image - File imgFile = new File("hello-world-QR.png"); // File path for output - ImageIO.write(img, "png", imgFile); // Write image to file + BufferedImage img = qr.toImage(10, 4); // Convert to bitmap image + File imgFile = new File("hello-world-QR.png"); // File path for output + ImageIO.write(img, "png", imgFile); // Write image to file - String svg = qr.toSvgString(4); // Convert to SVG XML code + String svg = qr.toSvgString(4); // Convert to SVG XML code Files.write(new File("hello-world-QR.svg").toPath(), svg.getBytes(StandardCharsets.UTF_8)); } From 509881ea9f7a8b73315800011a60584675f11356 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 05:56:31 +0000 Subject: [PATCH 43/85] Slightly tweaked demo program for clarity. --- src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java index 10ed286..f57836c 100644 --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java @@ -62,7 +62,8 @@ public final class QrCodeGeneratorDemo { ImageIO.write(img, "png", imgFile); // Write image to file String svg = qr.toSvgString(4); // Convert to SVG XML code - Files.write(new File("hello-world-QR.svg").toPath(), + File svgFile = new File("hello-world-QR.svg"); // File path for output + Files.write(svgFile.toPath(), // Write image to file svg.getBytes(StandardCharsets.UTF_8)); } From 132c8f32e22bc6d59e0ae0a20f200cc7b050c2e8 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 06:19:22 +0000 Subject: [PATCH 44/85] Added and updated many Javadoc and informal comments, synchronizing from the parent "QR Code generator library" project. --- src/io/nayuki/fastqrcodegen/BitBuffer.java | 19 +++ src/io/nayuki/fastqrcodegen/QrCode.java | 161 ++++++++++++++++++--- src/io/nayuki/fastqrcodegen/QrSegment.java | 67 ++++++++- 3 files changed, 222 insertions(+), 25 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java index 630ad5b..a2d573e 100644 --- a/src/io/nayuki/fastqrcodegen/BitBuffer.java +++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java @@ -27,6 +27,9 @@ import java.util.Arrays; import java.util.Objects; +/** + * An appendable sequence of bits (0s and 1s). Mainly used by {@link QrSegment}. + */ final class BitBuffer { /*---- Fields ----*/ @@ -39,6 +42,9 @@ final class BitBuffer { /*---- Constructor ----*/ + /** + * Constructs an empty bit buffer (length 0). + */ public BitBuffer() { data = new int[64]; bitLength = 0; @@ -48,6 +54,10 @@ final class BitBuffer { /*---- Methods ----*/ + /** + * Returns the length of this sequence, which is a non-negative value. + * @return the length of this sequence + */ public int getBit(int index) { if (index < 0 || index >= bitLength) throw new IndexOutOfBoundsException(); @@ -65,6 +75,15 @@ final class BitBuffer { } + /** + * Appends the specified number of low-order bits of the specified value to this + * buffer. Requires 0 ≤ len ≤ 31 and 0 ≤ val < 2len. + * @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) { if (len < 0 || len > 31 || val >>> len != 0) throw new IllegalArgumentException("Value out of range"); diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index ea295f4..42fdaec 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -29,10 +29,44 @@ import java.util.List; import java.util.Objects; +/** + * A QR Code symbol, which is a type of two-dimension barcode. + * Invented by Denso Wave and described in the ISO/IEC 18004 standard. + *

Instances of this class represent an immutable square grid of black and white cells. + * The class provides static factory functions to create a QR Code from text or binary data. + * The class covers the QR Code Model 2 specification, supporting all versions (sizes) + * from 1 to 40, all 4 error correction levels, and 4 character encoding modes.

+ *

Ways to create a QR Code object:

+ *
    + *
  • High level: Take the payload data and call {@link QrCode#encodeText(String,Ecc)} + * or {@link QrCode#encodeBinary(byte[],Ecc)}.

  • + *
  • Mid level: Custom-make the list of {@link QrSegment segments} + * and call {@link QrCode#encodeSegments(List,Ecc)} or + * {@link QrCode#encodeSegments(List,Ecc,int,int,int,boolean)}

  • + *
  • Low level: Custom-make the array of data codeword bytes (including segment headers and + * final padding, excluding error correction codewords), supply the appropriate version number, + * and call the {@link QrCode#QrCode(int,Ecc,byte[],int) constructor}.

  • + *
+ *

(Note that all ways require supplying the desired error correction level.)

+ * @see QrSegment + */ public final class QrCode { - /*---- Public static factory functions ----*/ + /*---- Static factory functions (high level) ----*/ + /** + * Returns a QR Code representing the specified Unicode text string at the specified error correction level. + * As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer + * Unicode code points (not UTF-16 code units) if the low error correction level is used. The smallest possible + * QR Code version is automatically chosen for the output. The ECC level of the result may be higher than the + * ecl argument if it can be done without increasing the version. + * @param text the text to be encoded (not {@code null}), which can be any Unicode string + * @param ecl the error correction level to use (not {@code null}) (boostable) + * @return a QR Code (not {@code null}) representing the text + * @throws NullPointerException if the text or error correction level is {@code null} + * @throws IllegalArgumentException if the text fails to fit in the + * largest version QR Code at the ECL, which means it is too long + */ public static QrCode encodeText(String text, Ecc ecl) { Objects.requireNonNull(text); Objects.requireNonNull(ecl); @@ -41,6 +75,18 @@ public final class QrCode { } + /** + * Returns a QR Code representing the specified binary data at the specified error correction level. + * This function always encodes using the binary segment mode, not any text mode. The maximum number of + * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. + * The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. + * @param data the binary data to encode (not {@code null}) + * @param ecl the error correction level to use (not {@code null}) (boostable) + * @return a QR Code (not {@code null}) representing the data + * @throws NullPointerException if the data or error correction level is {@code null} + * @throws IllegalArgumentException if the data fails to fit in the + * largest version QR Code at the ECL, which means it is too long + */ public static QrCode encodeBinary(byte[] data, Ecc ecl) { Objects.requireNonNull(data); Objects.requireNonNull(ecl); @@ -49,11 +95,51 @@ public final class QrCode { } + /*---- Static factory functions (mid level) ----*/ + + /** + * Returns a QR Code representing the specified segments at the specified error correction + * level. The smallest possible QR Code version is automatically chosen for the output. The ECC level + * of the result may be higher than the ecl argument if it can be done without increasing the version. + *

This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a mid-level API; the high-level API is {@link #encodeText(String,Ecc)} + * and {@link #encodeBinary(byte[],Ecc)}.

+ * @param segs the segments to encode + * @param ecl the error correction level to use (not {@code null}) (boostable) + * @return a QR Code (not {@code null}) representing the segments + * @throws NullPointerException if the list of segments, any segment, or the error correction level is {@code null} + * @throws IllegalArgumentException if the segments fail to fit in the + * largest version QR Code at the ECL, which means they are too long + */ public static QrCode encodeSegments(List segs, Ecc ecl) { return encodeSegments(segs, ecl, MIN_VERSION, MAX_VERSION, -1, true); } + /** + * Returns a QR Code representing the specified segments with the specified encoding parameters. + * The smallest possible QR Code version within the specified range is automatically + * chosen for the output. Iff boostEcl is {@code true}, then the ECC level of the + * result may be higher than the ecl argument if it can be done without increasing + * the version. The mask number is either between 0 to 7 (inclusive) to force that + * mask, or −1 to automatically choose an appropriate mask (which may be slow). + *

This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a mid-level API; the high-level API is {@link #encodeText(String,Ecc)} + * and {@link #encodeBinary(byte[],Ecc)}.

+ * @param segs the segments to encode + * @param ecl the error correction level to use (not {@code null}) (boostable) + * @param minVersion the minimum allowed version of the QR Code (at least 1) + * @param maxVersion the maximum allowed version of the QR Code (at most 40) + * @param mask the mask number to use (between 0 and 7 (inclusive)), or −1 for automatic mask + * @param boostEcl increases the ECC level as long as it doesn't increase the version number + * @return a QR Code (not {@code null}) representing the segments + * @throws NullPointerException if the list of segments, any segment, or the error correction level is {@code null} + * @throws IllegalArgumentException if 1 ≤ minVersion ≤ maxVersion ≤ 40 + * or −1 ≤ mask ≤ 7 is violated; or if the segments fail to + * fit in the maxVersion QR Code at the ECL, which means they are too long + */ public static QrCode encodeSegments(List segs, Ecc ecl, int minVersion, int maxVersion, int mask, boolean boostEcl) { Objects.requireNonNull(segs); Objects.requireNonNull(ecl); @@ -98,7 +184,7 @@ public final class QrCode { for (int padByte = 0xEC; bb.bitLength < dataCapacityBits; padByte ^= 0xEC ^ 0x11) bb.appendBits(padByte, 8); - // Create the QR Code symbol + // Create the QR Code object return new QrCode(version, ecl, bb.getBytes(), mask); } @@ -106,20 +192,41 @@ public final class QrCode { /*---- Instance fields ----*/ + /** The version number of this QR Code, which is between 1 and 40 (inclusive). + * This determines the size of this barcode. */ public final int version; + /** The width and height of this QR Code, measured in modules, between + * 21 and 177 (inclusive). This is equal to version × 4 + 17. */ public final int size; + /** The error correction level used in this QR Code, which is not {@code null}. */ public final Ecc errorCorrectionLevel; + /** The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). + *

Even if a QR Code is created with automatic masking requested (mask = + * −1), the resulting object still has a mask value between 0 and 7. */ public final int mask; private final int[] modules; - /*---- Constructor ----*/ + /*---- Constructor (low level) ----*/ + /** + * Constructs a QR Code with the specified version number, + * error correction level, data codeword bytes, and mask number. + *

This is a low-level API that most users should not use directly. A mid-level + * API is the {@link #encodeSegments(List,Ecc,int,int,int,boolean)} function.

+ * @param ver the version number to use, which must be in the range 1 to 40 (inclusive) + * @param ecl the error correction level to use + * @param dataCodewords the bytes representing segments to encode (without ECC) + * @param mask the mask pattern to use, which is either −1 for automatic choice or from 0 to 7 for fixed choice + * @throws NullPointerException if the byte array or error correction level is {@code null} + * @throws IllegalArgumentException if the version or mask value is out of range, + * or if the data is the wrong length for the specified version and error correction level + */ public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) { // Check arguments and initialize fields if (ver < MIN_VERSION || ver > MAX_VERSION) @@ -145,12 +252,13 @@ public final class QrCode { /*---- Public instance methods ----*/ /** - * Returns the color of the module (pixel) at the specified coordinates, which is either - * false for white or true for black. The top left corner has the coordinates (x=0, y=0). - * If the specified coordinates are out of bounds, then false (white) is returned. - * @param x the x coordinate, where 0 is the left edge and size−1 is the right edge - * @param y the y coordinate, where 0 is the top edge and size−1 is the bottom edge - * @return the module's color, which is either false (white) or true (black) + * Returns the color of the module (pixel) at the specified coordinates, which is {@code false} + * for white or {@code true} for black. The top left corner has the coordinates (x=0, y=0). + * If the specified coordinates are out of bounds, then {@code false} (white) is returned. + * @param x the x coordinate, where 0 is the left edge and size−1 is the right edge + * @param y the y coordinate, where 0 is the top edge and size−1 is the bottom edge + * @return {@code true} if the coordinates are in bounds and the module + * at that location is black, or {@code false} (white) otherwise */ public boolean getModule(int x, int y) { if (0 <= x && x < size && 0 <= y && y < size) { @@ -162,13 +270,13 @@ public final class QrCode { /** - * Returns a new image object representing this QR Code, with the specified module scale and number - * of border modules. For example, the arguments scale=10, border=4 means to pad the QR Code symbol - * with 4 white border modules on all four edges, then use 10*10 pixels to represent each module. + * Returns a raster image depicting this QR Code, with the specified module scale and border modules. + *

For example, toImage(scale=10, border=4) means to pad the QR Code with 4 white + * border modules on all four sides, and use 10×10 pixels to represent each module. * The resulting image only contains the hex colors 000000 and FFFFFF. - * @param scale the module scale factor, which must be positive + * @param scale the side length (measured in pixels, must be positive) of each module * @param border the number of border modules to add, which must be non-negative - * @return an image representing this QR Code, with padding and scaling + * @return a new image representing this QR Code, with padding and scaling * @throws IllegalArgumentException if the scale or border is out of range, or if * {scale, border, size} cause the image dimensions to exceed Integer.MAX_VALUE */ @@ -190,10 +298,10 @@ public final class QrCode { /** - * Returns a string of SVG XML code representing an image of this QR Code symbol with the specified - * number of border modules. Note that Unix newlines (\n) are always used, regardless of the platform. + * Returns a string of SVG code for an image depicting this QR Code, with the specified number + * of border modules. The string always uses Unix newlines (\n), regardless of the platform. * @param border the number of border modules to add, which must be non-negative - * @return a string representing this QR Code as an SVG document + * @return a string representing this QR Code as an SVG XML document * @throws IllegalArgumentException if the border is negative */ public String toSvgString(int border) { @@ -331,7 +439,7 @@ public final class QrCode { } - // A messy helper function for the constructors. 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. // This method applies and returns the actual mask chosen, from 0 to 7. private int handleConstructorMasking(int[][] masks, int mask) { @@ -434,7 +542,7 @@ public final class QrCode { - /*---- Private static helper functions ----*/ + /*---- Private helper functions ----*/ // 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. @@ -454,7 +562,10 @@ public final class QrCode { /*---- Constants and tables ----*/ + /** The minimum version number (1) supported in the QR Code Model 2 standard. */ public static final int MIN_VERSION = 1; + + /** The maximum version number (40) supported in the QR Code Model 2 standard. */ public static final int MAX_VERSION = 40; @@ -487,10 +598,16 @@ public final class QrCode { /*---- Public helper enumeration ----*/ + /** + * The error correction level in a QR Code symbol. + */ public enum Ecc { - // These enum constants must be declared in ascending order of error protection, - // for the sake of the implicit ordinal() method and values() function. - LOW(1), MEDIUM(0), QUARTILE(3), HIGH(2); + // Must be declared in ascending order of error protection + // so that the implicit ordinal() and values() work properly + /** The QR Code can tolerate about 7% erroneous codewords. */ LOW(1), + /** The QR Code can tolerate about 15% erroneous codewords. */ MEDIUM(0), + /** The QR Code can tolerate about 25% erroneous codewords. */ QUARTILE(3), + /** The QR Code can tolerate about 30% erroneous codewords. */ HIGH(2); // In the range 0 to 3 (unsigned 2-bit integer). final int formatBits; diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java index ff31a35..ee6a6a2 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegment.java +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -32,8 +32,17 @@ import java.util.Objects; public final class QrSegment { - /*---- Static factory functions ----*/ - + /*---- Static factory functions (mid level) ----*/ + + /** + * Returns a segment representing the specified binary data + * encoded in byte mode. All input byte arrays are acceptable. + *

Any text string can be converted to UTF-8 bytes ({@code + * s.getBytes(StandardCharsets.UTF_8)}) and encoded as a byte mode segment.

+ * @param data the binary data (not {@code null}) + * @return a segment (not {@code null}) containing the data + * @throws NullPointerException if the array is {@code null} + */ public static QrSegment makeBytes(byte[] data) { Objects.requireNonNull(data); int[] bits = new int[(data.length + 3) / 4]; @@ -43,6 +52,13 @@ public final class QrSegment { } + /** + * Returns a segment representing the specified string of decimal digits encoded in numeric mode. + * @param digits the text (not {@code null}), with only digits from 0 to 9 allowed + * @return a segment (not {@code null}) containing the text + * @throws NullPointerException if the string is {@code null} + * @throws IllegalArgumentException if the string contains non-digit characters + */ public static QrSegment makeNumeric(String digits) { Objects.requireNonNull(digits); BitBuffer bb = new BitBuffer(); @@ -66,6 +82,15 @@ public final class QrSegment { } + /** + * Returns a segment representing the specified text string encoded in alphanumeric mode. + * The characters allowed are: 0 to 9, A to Z (uppercase only), space, + * dollar, percent, asterisk, plus, hyphen, period, slash, colon. + * @param text the text (not {@code null}), with only certain characters allowed + * @return a segment (not {@code null}) containing the text + * @throws NullPointerException if the string is {@code null} + * @throws IllegalArgumentException if the string contains non-encodable characters + */ public static QrSegment makeAlphanumeric(String text) { Objects.requireNonNull(text); BitBuffer bb = new BitBuffer(); @@ -89,6 +114,13 @@ public final class QrSegment { } + /** + * Returns a list of zero or more segments to represent the specified Unicode text string. + * The result may use various segment modes and switch modes to optimize the length of the bit stream. + * @param text the text to be encoded, which can be any Unicode string + * @return a new mutable list (not {@code null}) of segments (not {@code null}) containing the text + * @throws NullPointerException if the text is {@code null} + */ public static List makeSegments(String text) { Objects.requireNonNull(text); @@ -105,6 +137,13 @@ public final class QrSegment { } + /** + * Returns a segment representing an Extended Channel Interpretation + * (ECI) designator with the specified assignment value. + * @param assignVal the ECI assignment number (see the AIM ECI specification) + * @return a segment (not {@code null}) containing the data + * @throws IllegalArgumentException if the value is outside the range [0, 106) + */ public static QrSegment makeEci(int assignVal) { BitBuffer bb = new BitBuffer(); if (0 <= assignVal && assignVal < (1 << 7)) @@ -144,17 +183,32 @@ public final class QrSegment { /*---- Instance fields ----*/ + /** The mode indicator of this segment. Not {@code null}. */ public final Mode mode; + /** The length of this segment's unencoded data. Measured in characters for + * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + * Always zero or positive. Not the same as the data's bit length. */ public final int numChars; + // The data bits of this segment. Not null. Accessed through getData(). final int[] data; final int bitLength; - /*---- Constructor ----*/ + /*---- Constructor (low level) ----*/ + /** + * Constructs a QR Code segment with the specified attributes and data. + * The character count (numCh) must agree with the mode and the bit buffer length, + * but the constraint isn't checked. The specified bit buffer is cloned and stored. + * @param md the mode (not {@code null}) + * @param numCh the data length in characters or bytes, which is non-negative + * @param data the data bits (not {@code null}) + * @throws NullPointerException if the mode or data is {@code null} + * @throws IllegalArgumentException if the character count is negative + */ public QrSegment(Mode md, int numCh, int[] data, int bitLen) { mode = Objects.requireNonNull(md); this.data = Objects.requireNonNull(data); @@ -204,6 +258,9 @@ public final class QrSegment { /*---- Public helper enumeration ----*/ + /** + * Describes how a segment's data bits are interpreted. + */ public enum Mode { /*-- Constants --*/ @@ -217,8 +274,10 @@ public final class QrSegment { /*-- Fields --*/ + // The mode indicator bits, which is a uint4 value (range 0 to 15). final int modeBits; + // Number of character count bits for three different version ranges. private final int[] numBitsCharCount; @@ -232,6 +291,8 @@ public final class QrSegment { /*-- Method --*/ + // Returns the bit width of the character count field for a segment in this mode + // in a QR Code at the given version number. The result is in the range [0, 16]. int numCharCountBits(int ver) { return numBitsCharCount[(ver + 7) / 17]; } From f6e7b1760271928de92ed932592ae5d657ed2e72 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 06:25:12 +0000 Subject: [PATCH 45/85] Tweaked small pieces of code, synchronizing from the parent "QR Code generator library" project. --- src/io/nayuki/fastqrcodegen/BitBuffer.java | 2 ++ src/io/nayuki/fastqrcodegen/QrCode.java | 1 + src/io/nayuki/fastqrcodegen/QrSegment.java | 11 +++++++---- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java index a2d573e..f187e9f 100644 --- a/src/io/nayuki/fastqrcodegen/BitBuffer.java +++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java @@ -87,6 +87,8 @@ final class BitBuffer { public void appendBits(int val, int len) { if (len < 0 || len > 31 || val >>> len != 0) throw new IllegalArgumentException("Value out of range"); + if (Integer.MAX_VALUE - bitLength < len) + throw new IllegalStateException("Maximum length reached"); if (bitLength + len + 1 > data.length << 5) data = Arrays.copyOf(data, data.length * 2); diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 42fdaec..4366d5f 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -381,6 +381,7 @@ public final class QrCode { // Returns a new byte string representing the given data with the appropriate error correction // codewords appended to it, based on this object's version and error correction level. private byte[] addEccAndInterleave(byte[] data) { + Objects.requireNonNull(data); if (data.length != getNumDataCodewords(version, errorCorrectionLevel)) throw new IllegalArgumentException(); diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java index ee6a6a2..9f91ac4 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegment.java +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -146,12 +146,14 @@ public final class QrSegment { */ public static QrSegment makeEci(int assignVal) { BitBuffer bb = new BitBuffer(); - if (0 <= assignVal && assignVal < (1 << 7)) + if (assignVal < 0) + throw new IllegalArgumentException("ECI assignment value out of range"); + else if (assignVal < (1 << 7)) bb.appendBits(assignVal, 8); - else if ((1 << 7) <= assignVal && assignVal < (1 << 14)) { + else if (assignVal < (1 << 14)) { bb.appendBits(2, 2); bb.appendBits(assignVal, 14); - } else if ((1 << 14) <= assignVal && assignVal < 1000000) { + } else if (assignVal < 1_000_000) { bb.appendBits(6, 3); bb.appendBits(assignVal, 21); } else @@ -284,7 +286,7 @@ public final class QrSegment { /*-- Constructor --*/ private Mode(int mode, int... ccbits) { - this.modeBits = mode; + modeBits = mode; numBitsCharCount = ccbits; } @@ -294,6 +296,7 @@ public final class QrSegment { // Returns the bit width of the character count field for a segment in this mode // in a QR Code at the given version number. The result is in the range [0, 16]. int numCharCountBits(int ver) { + assert QrCode.MIN_VERSION <= ver && ver <= QrCode.MAX_VERSION; return numBitsCharCount[(ver + 7) / 17]; } From b669311c2eed9c9afe5f8d6f54ca0da551cab927 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 06:33:33 +0000 Subject: [PATCH 46/85] Added Javadoc and informal comments to some members, which differ from the parent project. --- src/io/nayuki/fastqrcodegen/BitBuffer.java | 5 ++++ src/io/nayuki/fastqrcodegen/QrCode.java | 3 +++ src/io/nayuki/fastqrcodegen/QrSegment.java | 29 ++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java index f187e9f..7031914 100644 --- a/src/io/nayuki/fastqrcodegen/BitBuffer.java +++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java @@ -65,6 +65,11 @@ final class BitBuffer { } + /** + * Returns an array representing this buffer's bits packed into bytes + * 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() { if (bitLength % 8 != 0) throw new IllegalStateException("Data is not a whole number of bytes"); diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 4366d5f..a1e71e0 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -208,6 +208,7 @@ public final class QrCode { * −1), the resulting object still has a mask value between 0 and 7. */ public final int mask; + // The modules of this QR Code. Immutable after constructor finishes. Accessed through getModule(). private final int[] modules; @@ -363,6 +364,8 @@ public final class QrCode { } + // Sets the module at the given coordinates to the given color. + // Only used by the constructor. Coordinates must be in bounds. private void setModule(int x, int y, int black) { assert 0 <= x && x < size; assert 0 <= y && y < size; diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java index 9f91ac4..cf0d655 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegment.java +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -30,6 +30,18 @@ import java.util.List; import java.util.Objects; +/** + * A segment of character/binary/control data in a QR Code symbol. + * Instances of this class are immutable. + *

The mid-level way to create a segment is to take the payload data and call a + * static factory function such as {@link QrSegment#makeNumeric(String)}. The low-level + * way to create a segment is to custom-make the bit buffer and call the {@link + * QrSegment#QrSegment(Mode,int,int[],int) constructor} with appropriate values.

+ *

This segment class imposes no length restrictions, but QR Codes have restrictions. + * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + * Any segment longer than this is meaningless for the purpose of generating QR Codes. + * This class can represent kanji mode segments, but provides no help in encoding them.

+ */ public final class QrSegment { /*---- Static factory functions (mid level) ----*/ @@ -162,6 +174,14 @@ public final class QrSegment { } + /** + * Tests whether the specified string can be encoded as a segment in numeric mode. + * A string is encodable iff each character is in the range 0 to 9. + * @param text the string to test for encodability (not {@code null}) + * @return {@code true} iff each character is in the range 0 to 9. + * @throws NullPointerException if the string is {@code null} + * @see #makeNumeric(String) + */ public static boolean isNumeric(String text) { for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); @@ -172,6 +192,15 @@ public final class QrSegment { } + /** + * Tests whether the specified string can be encoded as a segment in alphanumeric mode. + * A string is encodable iff each character is in the following set: 0 to 9, A to Z + * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + * @param text the string to test for encodability (not {@code null}) + * @return {@code true} iff each character is in the alphanumeric mode character set + * @throws NullPointerException if the string is {@code null} + * @see #makeAlphanumeric(String) + */ public static boolean isAlphanumeric(String text) { for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); From 61241872480da6e6ab4306c8d5c16bb36031bc91 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 06:38:54 +0000 Subject: [PATCH 47/85] Refactored QrTemplate to use QrCode.getBit(). --- src/io/nayuki/fastqrcodegen/QrCode.java | 2 +- src/io/nayuki/fastqrcodegen/QrTemplate.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index a1e71e0..dcd958f 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -559,7 +559,7 @@ public final class QrCode { // Returns 0 or 1 based on the i'th bit of x. - private static int getBit(int x, int i) { + static int getBit(int x, int i) { return (x >>> i) & 1; } diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 41306d1..9ecbbbd 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -173,7 +173,7 @@ final class QrTemplate { // Draw two copies for (int i = 0; i < 18; i++) { - int bit = (bits >>> i) & 1; + int bit = QrCode.getBit(bits, i); int a = size - 11 + i % 3; int b = i / 3; darkenFunctionModule(a, b, bit); @@ -260,7 +260,7 @@ final class QrTemplate { assert 0 <= x && x < size; assert 0 <= y && y < size; int i = y * size + x; - return (grid[i >>> 5] >>> i) & 1; + return QrCode.getBit(grid[i >>> 5], i); } From 3e6381cea83f5c3a2c5f93beed59a3439981aba0 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 06:50:45 +0000 Subject: [PATCH 48/85] Renamed loop variables and swapped commutative arguments for clarity. --- src/io/nayuki/fastqrcodegen/QrTemplate.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 9ecbbbd..49aad5d 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -185,10 +185,10 @@ final class QrTemplate { // Draws a 9*9 finder pattern including the border separator, // with the center module at (x, y). Modules can be out of bounds. private void drawFinderPattern(int x, int y) { - for (int i = -4; i <= 4; i++) { - for (int j = -4; j <= 4; j++) { - int dist = Math.max(Math.abs(i), Math.abs(j)); // Chebyshev/infinity norm - int xx = x + j, yy = y + i; + for (int dy = -4; dy <= 4; dy++) { + for (int dx = -4; dx <= 4; dx++) { + int dist = Math.max(Math.abs(dx), Math.abs(dy)); // Chebyshev/infinity norm + int xx = x + dx, yy = y + dy; if (0 <= xx && xx < size && 0 <= yy && yy < size) darkenFunctionModule(xx, yy, (dist != 2 && dist != 4) ? 1 : 0); } @@ -199,9 +199,9 @@ final class QrTemplate { // Draws a 5*5 alignment pattern, with the center module // at (x, y). All modules must be in bounds. private void drawAlignmentPattern(int x, int y) { - for (int i = -2; i <= 2; i++) { - for (int j = -2; j <= 2; j++) - darkenFunctionModule(x + j, y + i, (Math.max(Math.abs(i), Math.abs(j)) != 1) ? 1 : 0); + for (int dy = -2; dy <= 2; dy++) { + for (int dx = -2; dx <= 2; dx++) + darkenFunctionModule(x + dx, y + dy, (Math.max(Math.abs(dx), Math.abs(dy)) != 1) ? 1 : 0); } } From eb072ba84e1bd0a29413d598bf8f40303f835746 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 06:52:30 +0000 Subject: [PATCH 49/85] Simplified small pieces of logic. --- src/io/nayuki/fastqrcodegen/QrTemplate.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 49aad5d..6c81748 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -125,9 +125,7 @@ final class QrTemplate { int numAlign = alignPatPos.length; for (int i = 0; i < numAlign; i++) { for (int j = 0; j < numAlign; j++) { - if (i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0) - continue; // Skip the three finder corners - else + if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0)) drawAlignmentPattern(alignPatPos[i], alignPatPos[j]); } } @@ -201,7 +199,7 @@ final class QrTemplate { private void drawAlignmentPattern(int x, int y) { for (int dy = -2; dy <= 2; dy++) { for (int dx = -2; dx <= 2; dx++) - darkenFunctionModule(x + dx, y + dy, (Math.max(Math.abs(dx), Math.abs(dy)) != 1) ? 1 : 0); + darkenFunctionModule(x + dx, y + dy, Math.abs(Math.max(Math.abs(dx), Math.abs(dy)) - 1)); } } From 83a3c3b957714ddd38d8e5a2e941a996a913381c Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 06:52:37 +0000 Subject: [PATCH 50/85] Added and updated comments. --- src/io/nayuki/fastqrcodegen/QrTemplate.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 6c81748..931d9b0 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -90,7 +90,7 @@ final class QrTemplate { final int[][] masks; final int[] dataOutputBitIndexes; - private int[] isFunction; // Discarded at end of constructor + private int[] isFunction; // Discarded when constructor finishes private QrTemplate(int ver) { @@ -125,6 +125,7 @@ final class QrTemplate { int numAlign = alignPatPos.length; for (int i = 0; i < numAlign; i++) { for (int j = 0; j < numAlign; j++) { + // Don't draw on the three finder corners if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0)) drawAlignmentPattern(alignPatPos[i], alignPatPos[j]); } @@ -262,6 +263,8 @@ final class QrTemplate { } + // Marks the module at the given coordinates as a function module. + // Also either sets that module black or keeps its color unchanged. private void darkenFunctionModule(int x, int y, int enable) { assert 0 <= x && x < size; assert 0 <= y && y < size; From b967ae4aaec57079e486ec0a67b6c5ef20cb54d1 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 07:00:35 +0000 Subject: [PATCH 51/85] Added and updated some strict overflow checks. --- src/io/nayuki/fastqrcodegen/BitBuffer.java | 6 ++++-- src/io/nayuki/fastqrcodegen/QrSegment.java | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java index 7031914..4a2faf1 100644 --- a/src/io/nayuki/fastqrcodegen/BitBuffer.java +++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java @@ -92,7 +92,7 @@ final class BitBuffer { public void appendBits(int val, int len) { if (len < 0 || len > 31 || val >>> len != 0) throw new IllegalArgumentException("Value out of range"); - if (Integer.MAX_VALUE - bitLength < len) + if (len > Integer.MAX_VALUE - bitLength) throw new IllegalStateException("Maximum length reached"); if (bitLength + len + 1 > data.length << 5) @@ -118,12 +118,14 @@ final class BitBuffer { Objects.requireNonNull(vals); if (len == 0) return; - if (len < 0 || len > vals.length * 32) + if (len < 0 || len > vals.length * 32L) throw new IllegalArgumentException("Value out of range"); int wholeWords = len / 32; int tailBits = len % 32; if (tailBits > 0 && vals[wholeWords] << tailBits != 0) throw new IllegalArgumentException("Last word must have low bits clear"); + if (len > Integer.MAX_VALUE - bitLength) + throw new IllegalStateException("Maximum length reached"); while (bitLength + len > data.length * 32) data = Arrays.copyOf(data, data.length * 2); diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java index cf0d655..bf7d5ef 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegment.java +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -57,6 +57,8 @@ public final class QrSegment { */ public static QrSegment makeBytes(byte[] data) { Objects.requireNonNull(data); + if (data.length * 8L > Integer.MAX_VALUE) + throw new IllegalArgumentException("Data too long"); int[] bits = new int[(data.length + 3) / 4]; for (int i = 0; i < data.length; i++) bits[i >>> 2] |= (data[i] & 0xFF) << (~i << 3); @@ -243,7 +245,7 @@ public final class QrSegment { public QrSegment(Mode md, int numCh, int[] data, int bitLen) { mode = Objects.requireNonNull(md); this.data = Objects.requireNonNull(data); - if (numCh < 0 || bitLen < 0 || bitLen > data.length * 32) + if (numCh < 0 || bitLen < 0 || bitLen > data.length * 32L) throw new IllegalArgumentException("Invalid value"); numChars = numCh; bitLength = bitLen; From 72d350e3a0d278e256f1e1f13623c910a1f7cdc3 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 07:04:53 +0000 Subject: [PATCH 52/85] Added and updated comments. --- src/io/nayuki/fastqrcodegen/BitBuffer.java | 8 ++++++++ src/io/nayuki/fastqrcodegen/QrSegment.java | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java index 4a2faf1..18e1027 100644 --- a/src/io/nayuki/fastqrcodegen/BitBuffer.java +++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java @@ -114,6 +114,14 @@ final class BitBuffer { } + /** + * Appends the specified sequence of bits to this buffer. + * Requires 0 ≤ len ≤ 32 × 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) { Objects.requireNonNull(vals); if (len == 0) diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java index bf7d5ef..677bcda 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegment.java +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -227,6 +227,7 @@ public final class QrSegment { // The data bits of this segment. Not null. Accessed through getData(). final int[] data; + // Requires 0 <= bitLength <= data.length * 32. final int bitLength; @@ -239,6 +240,7 @@ public final class QrSegment { * @param md the mode (not {@code null}) * @param numCh the data length in characters or bytes, which is non-negative * @param data the data bits (not {@code null}) + * @param bitLen the number of valid prefix bits in the data array * @throws NullPointerException if the mode or data is {@code null} * @throws IllegalArgumentException if the character count is negative */ From 5657ba274b21da3e4bba9f3bfb26a9044b448b92 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 7 Oct 2018 20:47:04 +0000 Subject: [PATCH 53/85] Added package summary Javadoc comment. --- src/io/nayuki/fastqrcodegen/package-info.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/io/nayuki/fastqrcodegen/package-info.java diff --git a/src/io/nayuki/fastqrcodegen/package-info.java b/src/io/nayuki/fastqrcodegen/package-info.java new file mode 100644 index 0000000..e70dc6c --- /dev/null +++ b/src/io/nayuki/fastqrcodegen/package-info.java @@ -0,0 +1,48 @@ +/** + * Generates QR Codes from text strings and byte arrays. + * + *

This library generates QR Code symbols, and its design is optimized for speed. It contrasts with another QR library by the same author which is slow but which optimizes for clarity and conciseness. The functionality of this library and its API are nearly identical to the slow library, but it runs anywhere from 1.5× to 6× as fast.

+ *

Home page for the fast library (design explanation, benchmarks): https://www.nayuki.io/page/fast-qr-code-generator-library

+ *

Home page for the slow library (live demo, QR Code introduction, competitor comparisons): https://www.nayuki.io/page/qr-code-generator-library

+ * + *

Features

+ *

Core features:

+ *
    + *
  • Available in 7 programming languages, all with nearly equal functionality: Java, JavaScript, TypeScript, Python, C++, C, Rust

  • + *
  • Significantly shorter code but more documentation comments compared to competing libraries

  • + *
  • Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard

  • + *
  • Output formats: Raw modules/pixels of the QR symbol, SVG XML string, {@code BufferedImage} raster bitmap

  • + *
  • Encodes numeric and special-alphanumeric text in less space than general text

  • + *
  • Open source code under the permissive MIT License

  • + *
+ *

Manual parameters:

+ *
    + *
  • User can specify minimum and maximum version numbers allowed, then library will automatically choose smallest version in the range that fits the data

  • + *
  • User can specify mask pattern manually, otherwise library will automatically evaluate all 8 masks and select the optimal one

  • + *
  • User can specify absolute error correction level, or allow the library to boost it if it doesn't increase the version number

  • + *
  • User can create a list of data segments manually and add ECI segments

  • + *
+ * + *

Examples

+ *

Simple operation:

+ *
import java.awt.image.BufferedImage;
+ *import java.io.File;
+ *import javax.imageio.ImageIO;
+ *import io.nayuki.fastqrcodegen.*;
+ *
+ *QrCode qr = QrCode.encodeText("Hello, world!", QrCode.Ecc.MEDIUM);
+ *BufferedImage img = qr.toImage(4, 10);
+ *ImageIO.write(img, "png", new File("qr-code.png"));
+ *

Manual operation:

+ *
import java.util.List;
+ *import io.nayuki.fastqrcodegen.*;
+ *
+ *List<QrSegment> segs = QrSegment.makeSegments("3141592653589793238462643383");
+ *QrCode qr = QrCode.encodeSegments(segs, QrCode.Ecc.HIGH, 5, 5, 2, false);
+ *for (int y = 0; y < qr.size; y++) {
+ *    for (int x = 0; x < qr.size; x++) {
+ *        (... paint qr.getModule(x, y) ...)
+ *    }
+ *}
+ */ +package io.nayuki.fastqrcodegen; From badeca0dc9c0eb84ed72585448cb090f7510b192 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 13 Oct 2018 21:22:52 +0000 Subject: [PATCH 54/85] Imported QrSegmentAdvanced from the parent project, changed a constant to be package-private, updated readme. --- Readme.markdown | 2 + src/io/nayuki/fastqrcodegen/QrSegment.java | 2 +- .../fastqrcodegen/QrSegmentAdvanced.java | 419 ++++++++++++++++++ 3 files changed, 422 insertions(+), 1 deletion(-) create mode 100644 src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java diff --git a/Readme.markdown b/Readme.markdown index 9e7380f..bd70143 100644 --- a/Readme.markdown +++ b/Readme.markdown @@ -20,6 +20,8 @@ Core features: * Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard * Output formats: Raw modules/pixels of the QR symbol, SVG XML string, `BufferedImage` raster bitmap * Encodes numeric and special-alphanumeric text in less space than general text +* Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes +* Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts * Open source code under the permissive MIT License Manual parameters: diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java index 677bcda..86cce95 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegment.java +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -276,7 +276,7 @@ public final class QrSegment { /*---- Constants ----*/ - private static final int[] ALPHANUMERIC_MAP; + static final int[] ALPHANUMERIC_MAP; static { final String ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; diff --git a/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java b/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java new file mode 100644 index 0000000..6cbdb7b --- /dev/null +++ b/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java @@ -0,0 +1,419 @@ +/* + * Fast QR Code generator library + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/fast-qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.fastqrcodegen; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Objects; +import io.nayuki.fastqrcodegen.QrSegment.Mode; + + +/** + * Splits text into optimal segments and encodes kanji segments. + * Provides static functions only; not instantiable. + * @see QrSegment + * @see QrCode + */ +public final class QrSegmentAdvanced { + + /*---- Optimal list of segments encoder ----*/ + + /** + * Returns a list of zero or more segments to represent the specified Unicode text string. + * The resulting list optimally minimizes the total encoded bit length, subjected to the constraints + * in the specified {error correction level, minimum version number, maximum version number}. + *

This function can utilize all four text encoding modes: numeric, alphanumeric, byte (UTF-8), + * and kanji. This can be considered as a sophisticated but slower replacement for {@link + * QrSegment#makeSegments(String)}. This requires more input parameters because it searches a + * range of versions, like {@link QrCode#encodeSegments(List,QrCode.Ecc,int,int,int,boolean)}.

+ * @param text the text to be encoded (not {@code null}), which can be any Unicode string + * @param ecl the error correction level to use (not {@code null}) + * @param minVersion the minimum allowed version of the QR Code (at least 1) + * @param maxVersion the maximum allowed version of the QR Code (at most 40) + * @return a new mutable list (not {@code null}) of segments (not {@code null}) + * containing the text, minimizing the bit length with respect to the constraints + * @throws NullPointerException if the text or error correction level is {@code null} + * @throws IllegalArgumentException if 1 ≤ minVersion ≤ maxVersion ≤ 40 + * is violated, or if the data is too long to fit in a QR Code at maxVersion at ECL + */ + public static List makeSegmentsOptimally(String text, QrCode.Ecc ecl, int minVersion, int maxVersion) { + // Check arguments + Objects.requireNonNull(text); + Objects.requireNonNull(ecl); + if (!(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION)) + throw new IllegalArgumentException("Invalid value"); + + // Iterate through version numbers, and make tentative segments + List segs = null; + int[] codePoints = toCodePoints(text); + for (int version = minVersion; version <= maxVersion; version++) { + if (version == minVersion || version == 10 || version == 27) + segs = makeSegmentsOptimally(codePoints, version); + assert segs != null; + + // Check if the segments fit + int dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8; + int dataUsedBits = QrSegment.getTotalBits(segs, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + return segs; + } + throw new IllegalArgumentException("Data too long"); + } + + + // Returns a new list of segments that is optimal for the given text at the given version number. + private static List makeSegmentsOptimally(int[] codePoints, int version) { + if (codePoints.length == 0) + return new ArrayList<>(); + Mode[] charModes = computeCharacterModes(codePoints, version); + return splitIntoSegments(codePoints, charModes); + } + + + // Returns a new array representing the optimal mode per code point based on the given text and version. + private static Mode[] computeCharacterModes(int[] codePoints, int version) { + if (codePoints.length == 0) + throw new IllegalArgumentException(); + final Mode[] modeTypes = {Mode.BYTE, Mode.ALPHANUMERIC, Mode.NUMERIC, Mode.KANJI}; // Do not modify + final int numModes = modeTypes.length; + + // Segment header sizes, measured in 1/6 bits + final int[] headCosts = new int[numModes]; + for (int i = 0; i < numModes; i++) + headCosts[i] = (4 + modeTypes[i].numCharCountBits(version)) * 6; + + // charModes[i][j] represents the mode to encode the code point at + // index i such that the final segment ends in modeTypes[j] and the + // total number of bits is minimized over all possible choices + Mode[][] charModes = new Mode[codePoints.length][numModes]; + + // At the beginning of each iteration of the loop below, + // prevCosts[j] is the exact minimum number of 1/6 bits needed to + // encode the entire string prefix of length i, and end in modeTypes[j] + int[] prevCosts = headCosts.clone(); + + // Calculate costs using dynamic programming + for (int i = 0; i < codePoints.length; i++) { + int c = codePoints[i]; + int[] curCosts = new int[numModes]; + { // Always extend a byte mode segment + curCosts[0] = prevCosts[0] + countUtf8Bytes(c) * 8 * 6; + charModes[i][0] = modeTypes[0]; + } + // Extend a segment if possible + if (QrSegment.ALPHANUMERIC_MAP[c] != -1) { // Is alphanumeric + curCosts[1] = prevCosts[1] + 33; // 5.5 bits per alphanumeric char + charModes[i][1] = modeTypes[1]; + } + if ('0' <= c && c <= '9') { // Is numeric + curCosts[2] = prevCosts[2] + 20; // 3.33 bits per digit + charModes[i][2] = modeTypes[2]; + } + if (isKanji(c)) { + curCosts[3] = prevCosts[3] + 78; // 13 bits per Shift JIS char + charModes[i][3] = modeTypes[3]; + } + + // Start new segment at the end to switch modes + for (int j = 0; j < numModes; j++) { // To mode + for (int k = 0; k < numModes; k++) { // From mode + int newCost = (curCosts[k] + 5) / 6 * 6 + headCosts[j]; + if (charModes[i][k] != null && (charModes[i][j] == null || newCost < curCosts[j])) { + curCosts[j] = newCost; + charModes[i][j] = modeTypes[k]; + } + } + } + + prevCosts = curCosts; + } + + // Find optimal ending mode + Mode curMode = null; + for (int i = 0, minCost = 0; i < numModes; i++) { + if (curMode == null || prevCosts[i] < minCost) { + minCost = prevCosts[i]; + curMode = modeTypes[i]; + } + } + + // Get optimal mode for each code point by tracing backwards + Mode[] result = new Mode[charModes.length]; + for (int i = result.length - 1; i >= 0; i--) { + for (int j = 0; j < numModes; j++) { + if (modeTypes[j] == curMode) { + curMode = charModes[i][j]; + result[i] = curMode; + break; + } + } + } + return result; + } + + + // Returns a new list of segments based on the given text and modes, such that + // consecutive code points in the same mode are put into the same segment. + private static List splitIntoSegments(int[] codePoints, Mode[] charModes) { + if (codePoints.length == 0) + throw new IllegalArgumentException(); + List result = new ArrayList<>(); + + // Accumulate run of modes + Mode curMode = charModes[0]; + int start = 0; + for (int i = 1; ; i++) { + if (i < codePoints.length && charModes[i] == curMode) + continue; + String s = new String(codePoints, start, i - start); + if (curMode == Mode.BYTE) + result.add(QrSegment.makeBytes(s.getBytes(StandardCharsets.UTF_8))); + else if (curMode == Mode.NUMERIC) + result.add(QrSegment.makeNumeric(s)); + else if (curMode == Mode.ALPHANUMERIC) + result.add(QrSegment.makeAlphanumeric(s)); + else if (curMode == Mode.KANJI) + result.add(makeKanji(s)); + else + throw new AssertionError(); + if (i >= codePoints.length) + return result; + curMode = charModes[i]; + start = i; + } + } + + + // Returns a new array of Unicode code points (effectively + // UTF-32 / UCS-4) representing the given UTF-16 string. + private static int[] toCodePoints(String s) { + int[] result = s.codePoints().toArray(); + for (int c : result) { + if (Character.isSurrogate((char)c)) + throw new IllegalArgumentException("Invalid UTF-16 string"); + } + return result; + } + + + // Returns the number of UTF-8 bytes needed to encode the given Unicode code point. + private static int countUtf8Bytes(int cp) { + if (cp < 0) throw new IllegalArgumentException("Invalid code point"); + else if (cp < 0x80) return 1; + else if (cp < 0x800) return 2; + else if (cp < 0x10000) return 3; + else if (cp < 0x110000) return 4; + else throw new IllegalArgumentException("Invalid code point"); + } + + + + /*---- Kanji mode segment encoder ----*/ + + /** + * Returns a segment representing the specified text string encoded in kanji mode. + * Broadly speaking, the set of encodable characters are {kanji used in Japan, + * hiragana, katakana, East Asian punctuation, full-width ASCII, Greek, Cyrillic}. + * Examples of non-encodable characters include {ordinary ASCII, half-width katakana, + * more extensive Chinese hanzi}. + * @param text the text (not {@code null}), with only certain characters allowed + * @return a segment (not {@code null}) containing the text + * @throws NullPointerException if the string is {@code null} + * @throws IllegalArgumentException if the string contains non-encodable characters + * @see #isEncodableAsKanji(String) + */ + public static QrSegment makeKanji(String text) { + Objects.requireNonNull(text); + BitBuffer bb = new BitBuffer(); + text.chars().forEachOrdered(c -> { + int val = UNICODE_TO_QR_KANJI[c]; + if (val == -1) + throw new IllegalArgumentException("String contains non-kanji-mode characters"); + bb.appendBits(val, 13); + }); + return new QrSegment(Mode.KANJI, text.length(), bb.data, bb.bitLength); + } + + + /** + * Tests whether the specified string can be encoded as a segment in kanji mode. + * Broadly speaking, the set of encodable characters are {kanji used in Japan, + * hiragana, katakana, East Asian punctuation, full-width ASCII, Greek, Cyrillic}. + * Examples of non-encodable characters include {ordinary ASCII, half-width katakana, + * more extensive Chinese hanzi}. + * @param text the string to test for encodability (not {@code null}) + * @return {@code true} iff each character is in the kanji mode character set + * @throws NullPointerException if the string is {@code null} + * @see #makeKanji(String) + */ + public static boolean isEncodableAsKanji(String text) { + Objects.requireNonNull(text); + return text.chars().allMatch( + c -> isKanji((char)c)); + } + + + private static boolean isKanji(int c) { + return c < UNICODE_TO_QR_KANJI.length && UNICODE_TO_QR_KANJI[c] != -1; + } + + + // Data derived from ftp://ftp.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/SHIFTJIS.TXT + private static final String PACKED_QR_KANJI_TO_UNICODE = + "MAAwATAC/wz/DjD7/xr/G/8f/wEwmzCcALT/QACo/z7/4/8/MP0w/jCdMJ4wA07dMAUwBjAHMPwgFSAQ/w8AXDAcIBb/XCAmICUgGCAZIBwgHf8I/wkwFDAV/zv/Pf9b/10wCDAJMAowCzAMMA0wDjAPMBAwEf8LIhIAsQDX//8A9/8dImD/HP8eImYiZyIeIjQmQiZA" + + "ALAgMiAzIQP/5f8EAKIAo/8F/wP/Bv8K/yAApyYGJgUlyyXPJc4lxyXGJaEloCWzJbIlvSW8IDswEiGSIZAhkSGTMBP/////////////////////////////IggiCyKGIocigiKDIioiKf////////////////////8iJyIoAKwh0iHUIgAiA///////////////////" + + "//////////8iICKlIxIiAiIHImEiUiJqImsiGiI9Ih0iNSIrIiz//////////////////yErIDAmbyZtJmogICAhALb//////////yXv/////////////////////////////////////////////////xD/Ef8S/xP/FP8V/xb/F/8Y/xn///////////////////8h" + + "/yL/I/8k/yX/Jv8n/yj/Kf8q/yv/LP8t/y7/L/8w/zH/Mv8z/zT/Nf82/zf/OP85/zr///////////////////9B/0L/Q/9E/0X/Rv9H/0j/Sf9K/0v/TP9N/07/T/9Q/1H/Uv9T/1T/Vf9W/1f/WP9Z/1r//////////zBBMEIwQzBEMEUwRjBHMEgwSTBKMEswTDBN" + + "ME4wTzBQMFEwUjBTMFQwVTBWMFcwWDBZMFowWzBcMF0wXjBfMGAwYTBiMGMwZDBlMGYwZzBoMGkwajBrMGwwbTBuMG8wcDBxMHIwczB0MHUwdjB3MHgweTB6MHswfDB9MH4wfzCAMIEwgjCDMIQwhTCGMIcwiDCJMIowizCMMI0wjjCPMJAwkTCSMJP/////////////" + + "////////////////////////MKEwojCjMKQwpTCmMKcwqDCpMKowqzCsMK0wrjCvMLAwsTCyMLMwtDC1MLYwtzC4MLkwujC7MLwwvTC+ML8wwDDBMMIwwzDEMMUwxjDHMMgwyTDKMMswzDDNMM4wzzDQMNEw0jDTMNQw1TDWMNcw2DDZMNow2zDcMN0w3jDf//8w4DDh" + + "MOIw4zDkMOUw5jDnMOgw6TDqMOsw7DDtMO4w7zDwMPEw8jDzMPQw9TD2/////////////////////wORA5IDkwOUA5UDlgOXA5gDmQOaA5sDnAOdA54DnwOgA6EDowOkA6UDpgOnA6gDqf////////////////////8DsQOyA7MDtAO1A7YDtwO4A7kDugO7A7wDvQO+" + + "A78DwAPBA8MDxAPFA8YDxwPIA8n/////////////////////////////////////////////////////////////////////////////////////////////////////////////BBAEEQQSBBMEFAQVBAEEFgQXBBgEGQQaBBsEHAQdBB4EHwQgBCEEIgQjBCQEJQQm" + + "BCcEKAQpBCoEKwQsBC0ELgQv////////////////////////////////////////BDAEMQQyBDMENAQ1BFEENgQ3BDgEOQQ6BDsEPAQ9//8EPgQ/BEAEQQRCBEMERARFBEYERwRIBEkESgRLBEwETQROBE///////////////////////////////////yUAJQIlDCUQ" + + "JRglFCUcJSwlJCU0JTwlASUDJQ8lEyUbJRclIyUzJSslOyVLJSAlLyUoJTclPyUdJTAlJSU4JUL/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "/////////////////////////////////////06cVRZaA5Y/VMBhG2MoWfaQIoR1gxx6UGCqY+FuJWXthGaCppv1aJNXJ2WhYnFbm1nQhnuY9H1ifb6bjmIWfJ+It1uJXrVjCWaXaEiVx5eNZ09O5U8KT01PnVBJVvJZN1nUWgFcCWDfYQ9hcGYTaQVwunVPdXB5+32t" + + "fe+Aw4QOiGOLApBVkHpTO06VTqVX34CykMF4704AWPFuopA4ejKDKIKLnC9RQVNwVL1U4VbgWftfFZjybeuA5IUt////////lmKWcJagl/tUC1PzW4dwz3+9j8KW6FNvnVx6uk4ReJOB/G4mVhhVBGsdhRqcO1nlU6ltZnTclY9WQk6RkEuW8oNPmQxT4VW2WzBfcWYg" + + "ZvNoBGw4bPNtKXRbdsh6Tpg0gvGIW4pgku1tsnWrdsqZxWCmiwGNipWyaY5TrVGG//9XElgwWURbtF72YChjqWP0bL9vFHCOcRRxWXHVcz9+AYJ2gtGFl5BgkludG1hpZbxsWnUlUflZLlllX4Bf3GK8ZfpqKmsna7Rzi3/BiVadLJ0OnsRcoWyWg3tRBFxLYbaBxmh2" + + "cmFOWU/6U3hgaW4pek+X804LUxZO7k9VTz1PoU9zUqBT71YJWQ9awVu2W+F50WaHZ5xntmtMbLNwa3PCeY15vno8e4eCsYLbgwSDd4Pvg9OHZoqyVimMqI/mkE6XHoaKT8Rc6GIRcll1O4Hlgr2G/ozAlsWZE5nVTstPGonjVt5YSljKXvtf62AqYJRgYmHQYhJi0GU5" + + "////////m0FmZmiwbXdwcHVMdoZ9dYKlh/mVi5aOjJ1R8VK+WRZUs1uzXRZhaGmCba94jYTLiFeKcpOnmrhtbJmohtlXo2f/hs6SDlKDVodUBF7TYuFkuWg8aDhru3NyeLp6a4maidKNa48DkO2Vo5aUl2lbZlyzaX2YTZhOY5t7IGor//9qf2i2nA1vX1JyVZ1gcGLs" + + "bTtuB27RhFuJEI9EThScOVP2aRtqOpeEaCpRXHrDhLKR3JOMVludKGgigwWEMXylUgiCxXTmTn5Pg1GgW9JSClLYUudd+1WaWCpZ5luMW5hb215yXnlgo2EfYWNhvmPbZWJn0WhTaPprPmtTbFdvIm+Xb0V0sHUYduN3C3r/e6F8IX3pfzZ/8ICdgmaDnomzisyMq5CE" + + "lFGVk5WRlaKWZZfTmSiCGE44VCtcuF3Mc6l2THc8XKl/640LlsGYEZhUmFhPAU8OU3FVnFZoV/pZR1sJW8RckF4MXn5fzGPuZzpl12XiZx9oy2jE////////al9eMGvFbBdsfXV/eUhbY3oAfQBfvYmPihiMtI13jsyPHZjimg6bPE6AUH1RAFmTW5xiL2KAZOxrOnKg" + + "dZF5R3+ph/uKvItwY6yDypegVAlUA1WraFRqWIpweCdndZ7NU3RbooEahlCQBk4YTkVOx08RU8pUOFuuXxNgJWVR//9nPWxCbHJs43B4dAN6dnquewh9Gnz+fWZl53JbU7tcRV3oYtJi4GMZbiCGWooxjd2S+G8BeaabWk6oTqtOrE+bT6BQ0VFHevZRcVH2U1RTIVN/" + + "U+tVrFiDXOFfN19KYC9gUGBtYx9lWWpLbMFywnLtd++A+IEFggiFTpD3k+GX/5lXmlpO8FHdXC1mgWltXEBm8ml1c4loUHyBUMVS5FdHXf6TJmWkayNrPXQ0eYF5vXtLfcqCuYPMiH+JX4s5j9GR0VQfkoBOXVA2U+VTOnLXc5Z36YLmjq+ZxpnImdJRd2Eahl5VsHp6" + + "UHZb05BHloVOMmrbkedcUVxI////////Y5h6n2yTl3SPYXqqcYqWiHyCaBd+cGhRk2xS8lQbhauKE3+kjs2Q4VNmiIh5QU/CUL5SEVFEVVNXLXPqV4tZUV9iX4RgdWF2YWdhqWOyZDplbGZvaEJuE3Vmej18+31MfZl+S39rgw6DSobNigiKY4tmjv2YGp2PgriPzpvo" + + "//9Sh2IfZINvwJaZaEFQkWsgbHpvVHp0fVCIQIojZwhO9lA5UCZQZVF8UjhSY1WnVw9YBVrMXvphsmH4YvNjcmkcailyfXKscy54FHhvfXl3DICpiYuLGYzijtKQY5N1lnqYVZoTnnhRQ1OfU7Nee18mbhtukHOEc/59Q4I3igCK+pZQTk5QC1PkVHxW+lnRW2Rd8V6r" + + "XydiOGVFZ69uVnLQfMqItIChgOGD8IZOioeN6JI3lseYZ58TTpROkk8NU0hUSVQ+Wi9fjF+hYJ9op2qOdFp4gYqeiqSLd5GQTl6byU6kT3xPr1AZUBZRSVFsUp9SuVL+U5pT41QR////////VA5ViVdRV6JZfVtUW11bj13lXedd9154XoNeml63XxhgUmFMYpdi2GOn" + + "ZTtmAmZDZvRnbWghaJdpy2xfbSptaW4vbp11MnaHeGx6P3zgfQV9GH1efbGAFYADgK+AsYFUgY+CKoNSiEyIYYsbjKKM/JDKkXWScXg/kvyVpJZN//+YBZmZmtidO1JbUqtT91QIWNVi92/gjGqPX565UUtSO1RKVv16QJF3nWCe0nNEbwmBcHURX/1g2pqoctuPvGtk" + + "mANOylbwV2RYvlpaYGhhx2YPZgZoOWixbfd11X06gm6bQk6bT1BTyVUGXW9d5l3uZ/tsmXRzeAKKUJOWiN9XUF6nYytQtVCsUY1nAFTJWF5Zu1uwX2liTWOhaD1rc24IcH2Rx3KAeBV4JnltZY59MIPciMGPCZabUmRXKGdQf2qMoVG0V0KWKlg6aYqAtFSyXQ5X/HiV" + + "nfpPXFJKVItkPmYoZxRn9XqEe1Z9IpMvaFybrXs5UxlRilI3////////W99i9mSuZOZnLWu6hamW0XaQm9ZjTJMGm6t2v2ZSTglQmFPCXHFg6GSSZWNoX3Hmc8p1I3uXfoKGlYuDjNuReJkQZaxmq2uLTtVO1E86T39SOlP4U/JV41bbWOtZy1nJWf9bUFxNXgJeK1/X" + + "YB1jB2UvW1xlr2W9ZehnnWti//9re2wPc0V5SXnBfPh9GX0rgKKBAoHziZaKXoppimaKjIrujMeM3JbMmPxrb06LTzxPjVFQW1db+mFIYwFmQmshbstsu3I+dL111HjBeTqADIAzgeqElI+ebFCef18Pi1idK3r6jvhbjZbrTgNT8Vf3WTFayVukYIluf28Gdb6M6luf" + + "hQB74FByZ/SCnVxhhUp+HoIOUZlcBGNojWZlnHFueT59F4AFix2OypBuhseQqlAfUvpcOmdTcHxyNZFMkciTK4LlW8JfMWD5TjtT1luIYktnMWuKculz4HougWuNo5FSmZZRElPXVGpb/2OIajl9rJcAVtpTzlRo////////W5dcMV3eT+5hAWL+bTJ5wHnLfUJ+TX/S" + + "ge2CH4SQiEaJcouQjnSPL5AxkUuRbJbGkZxOwE9PUUVTQV+TYg5n1GxBbgtzY34mkc2Sg1PUWRlbv23ReV1+LnybWH5xn1H6iFOP8E/KXPtmJXeseuOCHJn/UcZfqmXsaW9riW3z//9ulm9kdv59FF3hkHWRh5gGUeZSHWJAZpFm2W4aXrZ90n9yZviFr4X3ivhSqVPZ" + + "WXNej1+QYFWS5JZkULdRH1LdUyBTR1PsVOhVRlUxVhdZaFm+WjxbtVwGXA9cEVwaXoReil7gX3Bif2KEYttjjGN3ZgdmDGYtZnZnfmiiah9qNWy8bYhuCW5YcTxxJnFndcd3AXhdeQF5ZXnweuB7EXynfTmAloPWhIuFSYhdiPOKH4o8ilSKc4xhjN6RpJJmk36UGJac" + + "l5hOCk4ITh5OV1GXUnBXzlg0WMxbIl44YMVk/mdhZ1ZtRHK2dXN6Y4S4i3KRuJMgVjFX9Jj+////////Yu1pDWuWce1+VIB3gnKJ5pjfh1WPsVw7TzhP4U+1VQdaIFvdW+lfw2FOYy9lsGZLaO5pm214bfF1M3W5dx95XnnmfTOB44KvhaqJqoo6jquPm5Aykd2XB066" + + "TsFSA1h1WOxcC3UaXD2BTooKj8WWY5dteyWKz5gIkWJW81Oo//+QF1Q5V4JeJWOobDRwindhfIt/4IhwkEKRVJMQkxiWj3RemsRdB11pZXBnoo2olttjbmdJaRmDxZgXlsCI/m+EZHpb+E4WcCx1XWYvUcRSNlLiWdNfgWAnYhBlP2V0Zh9mdGjyaBZrY24FcnJ1H3bb" + + "fL6AVljwiP2Jf4qgipOKy5AdkZKXUpdZZYl6DoEGlrteLWDcYhplpWYUZ5B383pNfE1+PoEKjKyNZI3hjl94qVIHYtljpWRCYpiKLXqDe8CKrJbqfXaCDIdJTtlRSFNDU2Bbo1wCXBZd3WImYkdksGgTaDRsyW1FbRdn029ccU5xfWXLen97rX3a////////fkp/qIF6" + + "ghuCOYWmim6Mzo31kHiQd5KtkpGVg5uuUk1VhG84cTZRaHmFflWBs3zOVkxYUVyoY6pm/mb9aVpy2XWPdY55DnlWed98l30gfUSGB4o0ljuQYZ8gUOdSdVPMU+JQCVWqWO5ZT3I9W4tcZFMdYONg82NcY4NjP2O7//9kzWXpZvld42nNaf1vFXHlTol16Xb4epN8333P" + + "fZyAYYNJg1iEbIS8hfuIxY1wkAGQbZOXlxyaElDPWJdhjoHThTWNCJAgT8NQdFJHU3Ngb2NJZ19uLI2zkB9P11xejMplz32aU1KIllF2Y8NbWFtrXApkDWdRkFxO1lkaWSpscIpRVT5YFVmlYPBiU2fBgjVpVZZAmcSaKE9TWAZb/oAQXLFeL1+FYCBhS2I0Zv9s8G7e" + + "gM6Bf4LUiIuMuJAAkC6Wip7bm9tO41PwWSd7LJGNmEyd+W7dcCdTU1VEW4ViWGKeYtNsom/vdCKKF5Q4b8GK/oM4UeeG+FPq////////U+lPRpBUj7BZaoExXf166o+/aNqMN3L4nEhqPYqwTjlTWFYGV2ZixWOiZeZrTm3hbltwrXfteu97qn27gD2AxobLipWTW1bj" + + "WMdfPmWtZpZqgGu1dTeKx1Akd+VXMF8bYGVmemxgdfR6Gn9ugfSHGJBFmbN7yXVcevl7UYTE//+QEHnpepKDNlrhd0BOLU7yW5lf4GK9Zjxn8WzohmuId4o7kU6S85nQahdwJnMqgueEV4yvTgFRRlHLVYtb9V4WXjNegV8UXzVfa1+0YfJjEWaiZx1vbnJSdTp3OoB0" + + "gTmBeId2ir+K3I2FjfOSmpV3mAKc5VLFY1d29GcVbIhzzYzDk66Wc20lWJxpDmnMj/2TmnXbkBpYWmgCY7Rp+09Dbyxn2I+7hSZ9tJNUaT9vcFdqWPdbLH0scipUCpHjnbROrU9OUFxQdVJDjJ5USFgkW5peHV6VXq1e918fYIxitWM6Y9Bor2xAeId5jnoLfeCCR4oC" + + "iuaORJAT////////kLiRLZHYnw5s5WRYZOJldW70doR7G5Bpk9FuulTyX7lkpI9Nj+2SRFF4WGtZKVxVXpdt+36PdRyMvI7imFtwuU8da79vsXUwlvtRTlQQWDVYV1msXGBfkmWXZ1xuIXZ7g9+M7ZAUkP2TTXgleDpSql6mVx9ZdGASUBJRWlGs//9RzVIAVRBYVFhY" + + "WVdblVz2XYtgvGKVZC1ncWhDaLxo33bXbdhub22bcG9xyF9Tddh5d3tJe1R7UnzWfXFSMIRjhWmF5IoOiwSMRo4PkAOQD5QZlnaYLZowldhQzVLVVAxYAlwOYadknm0ed7N65YD0hASQU5KFXOCdB1M/X5dfs22ccnl3Y3m/e+Rr0nLsiq1oA2phUfh6gWk0XEqc9oLr" + + "W8WRSXAeVnhcb2DHZWZsjIxakEGYE1RRZseSDVlIkKNRhU5NUeqFmYsOcFhjepNLaWKZtH4EdXdTV2lgjt+W42xdToxcPF8Qj+lTAozRgImGeV7/ZeVOc1Fl////////WYJcP5fuTvtZil/Nio1v4XmweWJb54RxcytxsV50X/Vje2SaccN8mE5DXvxOS1fcVqJgqW/D" + + "fQ2A/YEzgb+PsomXhqRd9GKKZK2Jh2d3bOJtPnQ2eDRaRn91gq2ZrE/zXsNi3WOSZVdnb3bDckyAzIC6jymRTVANV/lakmiF//9pc3Fkcv2Mt1jyjOCWapAZh3955HfnhClPL1JlU1pizWfPbMp2fXuUfJWCNoWEj+tm3W8gcgZ+G4OrmcGeplH9e7F4cnu4gId7SGro" + + "XmGAjHVRdWBRa5Jibox2epGXmupPEH9wYpx7T5WlnOlWelhZhuSWvE80UiRTSlPNU9teBmQsZZFnf2w+bE5ySHKvc+11VH5BgiyF6Yype8SRxnFpmBKY72M9Zml1anbkeNCFQ4buUypTUVQmWYNeh198YLJiSWJ5YqtlkGvUbMx1snaueJF52H3Lf3eApYirirmMu5B/" + + "l16Y22oLfDhQmVw+X65nh2vYdDV3CX+O////////nztnynoXUzl1i5rtX2aBnYPxgJhfPF/FdWJ7RpA8aGdZ61qbfRB2fossT/VfamoZbDdvAnTieWiIaIpVjHle32PPdcV50oLXkyiS8oSchu2cLVTBX2xljG1ccBWMp4zTmDtlT3T2Tg1O2FfgWStaZlvMUaheA16c" + + "YBZidmV3//9lp2ZubW5yNnsmgVCBmoKZi1yMoIzmjXSWHJZET65kq2tmgh6EYYVqkOhcAWlTmKiEeoVXTw9Sb1+pXkVnDXmPgXmJB4mGbfVfF2JVbLhOz3Jpm5JSBlQ7VnRYs2GkYm5xGllufIl83n0blvBlh4BeThlPdVF1WEBeY15zXwpnxE4mhT2ViZZbfHOYAVD7" + + "WMF2VninUiV3pYURe4ZQT1kJckd7x33oj7qP1JBNT79SyVopXwGXrU/dgheS6lcDY1VraXUriNyPFHpCUt9Yk2FVYgpmrmvNfD+D6VAjT/hTBVRGWDFZSVudXPBc710pXpZisWNnZT5luWcL////////bNVs4XD5eDJ+K4DegrOEDITshwKJEooqjEqQppLSmP2c851s" + + "Tk9OoVCNUlZXSlmoXj1f2F/ZYj9mtGcbZ9Bo0lGSfSGAqoGoiwCMjIy/kn6WMlQgmCxTF1DVU1xYqGSyZzRyZ3dmekaR5lLDbKFrhlgAXkxZVGcsf/tR4XbG//9kaXjom1Seu1fLWblmJ2eaa85U6WnZXlWBnGeVm6pn/pxSaF1Opk/jU8hiuWcrbKuPxE+tfm2ev04H" + + "YWJugG8rhRNUc2cqm0Vd83uVXKxbxoccbkqE0XoUgQhZmXyNbBF3IFLZWSJxIXJfd9uXJ51haQtaf1oYUaVUDVR9Zg5234/3kpic9Fnqcl1uxVFNaMl9v33sl2KeumR4aiGDAlmEW19r23MbdvJ9soAXhJlRMmcontl27mdiUv+ZBVwkYjt8foywVU9gtn0LlYBTAU5f" + + "UbZZHHI6gDaRzl8ld+JThF95fQSFrIozjo2XVmfzha6UU2EJYQhsuXZS////////iu2POFUvT1FRKlLHU8tbpV59YKBhgmPWZwln2m5nbYxzNnM3dTF5UIjVipiQSpCRkPWWxIeNWRVOiE9ZTg6KiY8/mBBQrV58WZZbuV64Y9pj+mTBZtxpSmnYbQtutnGUdSh6r3+K" + + "gACESYTJiYGLIY4KkGWWfZkKYX5ikWsy//9sg210f8x//G3Af4WHuoj4Z2WDsZg8lvdtG31hhD2Rak5xU3VdUGsEb+uFzYYtiadSKVQPXGVnTmiodAZ0g3XiiM+I4ZHMluKWeF+Lc4d6y4ROY6B1ZVKJbUFunHQJdVl4a3ySloZ63J+NT7ZhbmXFhlxOhk6uUNpOIVHM" + + "W+5lmWiBbbxzH3ZCd616HHzngm+K0pB8kc+WdZgYUpt90VArU5hnl23LcdB0M4HojyqWo5xXnp90YFhBbZl9L5heTuRPNk+LUbdSsV26YBxzsnk8gtOSNJa3lvaXCp6Xn2Jmpmt0UhdSo3DIiMJeyWBLYZBvI3FJfD599IBv////////hO6QI5MsVEKbb2rTcImMwo3v" + + "lzJStFpBXspfBGcXaXxplG1qbw9yYnL8e+2AAYB+h0uQzlFtnpN5hICLkzKK1lAtVIyKcWtqjMSBB2DRZ6Cd8k6ZTpicEIprhcGFaGkAbn54l4FV////////////////////////////////////////////////////////////////////////////////////////" + + "/////////////////////////////18MThBOFU4qTjFONk48Tj9OQk5WTlhOgk6FjGtOioISXw1Ojk6eTp9OoE6iTrBOs062Ts5OzU7ETsZOwk7XTt5O7U7fTvdPCU9aTzBPW09dT1dPR092T4hPj0+YT3tPaU9wT5FPb0+GT5ZRGE/UT99Pzk/YT9tP0U/aT9BP5E/l" + + "UBpQKFAUUCpQJVAFTxxP9lAhUClQLE/+T+9QEVAGUENQR2cDUFVQUFBIUFpQVlBsUHhQgFCaUIVQtFCy////////UMlQylCzUMJQ1lDeUOVQ7VDjUO5Q+VD1UQlRAVECURZRFVEUURpRIVE6UTdRPFE7UT9RQFFSUUxRVFFievhRaVFqUW5RgFGCVthRjFGJUY9RkVGT" + + "UZVRllGkUaZRolGpUapRq1GzUbFRslGwUbVRvVHFUclR21HghlVR6VHt//9R8FH1Uf5SBFILUhRSDlInUipSLlIzUjlST1JEUktSTFJeUlRSalJ0UmlSc1J/Un1SjVKUUpJScVKIUpGPqI+nUqxSrVK8UrVSwVLNUtdS3lLjUuaY7VLgUvNS9VL4UvlTBlMIdThTDVMQ" + + "Uw9TFVMaUyNTL1MxUzNTOFNAU0ZTRU4XU0lTTVHWU15TaVNuWRhTe1N3U4JTllOgU6ZTpVOuU7BTtlPDfBKW2VPfZvxx7lPuU+hT7VP6VAFUPVRAVCxULVQ8VC5UNlQpVB1UTlSPVHVUjlRfVHFUd1RwVJJUe1SAVHZUhFSQVIZUx1SiVLhUpVSsVMRUyFSo////////" + + "VKtUwlSkVL5UvFTYVOVU5lUPVRRU/VTuVO1U+lTiVTlVQFVjVUxVLlVcVUVVVlVXVThVM1VdVZlVgFSvVYpVn1V7VX5VmFWeVa5VfFWDValVh1WoVdpVxVXfVcRV3FXkVdRWFFX3VhZV/lX9VhtV+VZOVlBx31Y0VjZWMlY4//9Wa1ZkVi9WbFZqVoZWgFaKVqBWlFaP" + + "VqVWrla2VrRWwla8VsFWw1bAVshWzlbRVtNW11buVvlXAFb/VwRXCVcIVwtXDVcTVxhXFlXHVxxXJlc3VzhXTlc7V0BXT1dpV8BXiFdhV39XiVeTV6BXs1ekV6pXsFfDV8ZX1FfSV9NYClfWV+NYC1gZWB1YclghWGJYS1hwa8BYUlg9WHlYhVi5WJ9Yq1i6WN5Yu1i4" + + "WK5YxVjTWNFY11jZWNhY5VjcWORY31jvWPpY+Vj7WPxY/VkCWQpZEFkbaKZZJVksWS1ZMlk4WT560llVWVBZTllaWVhZYllgWWdZbFlp////////WXhZgVmdT15Pq1mjWbJZxlnoWdxZjVnZWdpaJVofWhFaHFoJWhpaQFpsWklaNVo2WmJaalqaWrxavlrLWsJavVrj" + + "Wtda5lrpWtZa+lr7WwxbC1sWWzJa0FsqWzZbPltDW0VbQFtRW1VbWltbW2VbaVtwW3NbdVt4ZYhbeluA//9bg1umW7hbw1vHW8lb1FvQW+Rb5lviW95b5VvrW/Bb9lvzXAVcB1wIXA1cE1wgXCJcKFw4XDlcQVxGXE5cU1xQXE9bcVxsXG5OYlx2XHlcjFyRXJRZm1yr" + + "XLtctly8XLdcxVy+XMdc2VzpXP1c+lztXYxc6l0LXRVdF11cXR9dG10RXRRdIl0aXRldGF1MXVJdTl1LXWxdc112XYddhF2CXaJdnV2sXa5dvV2QXbddvF3JXc1d013SXdZd213rXfJd9V4LXhpeGV4RXhteNl43XkReQ15AXk5eV15UXl9eYl5kXkdedV52XnqevF5/" + + "XqBewV7CXshe0F7P////////XtZe417dXtpe217iXuFe6F7pXuxe8V7zXvBe9F74Xv5fA18JX11fXF8LXxFfFl8pXy1fOF9BX0hfTF9OXy9fUV9WX1dfWV9hX21fc193X4Nfgl9/X4pfiF+RX4dfnl+ZX5hfoF+oX61fvF/WX/tf5F/4X/Ff3WCzX/9gIWBg//9gGWAQ" + + "YClgDmAxYBtgFWArYCZgD2A6YFpgQWBqYHdgX2BKYEZgTWBjYENgZGBCYGxga2BZYIFgjWDnYINgmmCEYJtglmCXYJJgp2CLYOFguGDgYNNgtF/wYL1gxmC1YNhhTWEVYQZg9mD3YQBg9GD6YQNhIWD7YPFhDWEOYUdhPmEoYSdhSmE/YTxhLGE0YT1hQmFEYXNhd2FY" + + "YVlhWmFrYXRhb2FlYXFhX2FdYVNhdWGZYZZhh2GsYZRhmmGKYZFhq2GuYcxhymHJYfdhyGHDYcZhumHLf3lhzWHmYeNh9mH6YfRh/2H9Yfxh/mIAYghiCWINYgxiFGIb////////Yh5iIWIqYi5iMGIyYjNiQWJOYl5iY2JbYmBiaGJ8YoJiiWJ+YpJik2KWYtRig2KU" + + "Ytdi0WK7Ys9i/2LGZNRiyGLcYsxiymLCYsdim2LJYwxi7mLxYydjAmMIYu9i9WNQYz5jTWQcY09jlmOOY4Bjq2N2Y6Njj2OJY59jtWNr//9jaWO+Y+ljwGPGY+NjyWPSY/ZjxGQWZDRkBmQTZCZkNmUdZBdkKGQPZGdkb2R2ZE5lKmSVZJNkpWSpZIhkvGTaZNJkxWTH" + + "ZLtk2GTCZPFk54IJZOBk4WKsZONk72UsZPZk9GTyZPplAGT9ZRhlHGUFZSRlI2UrZTRlNWU3ZTZlOHVLZUhlVmVVZU1lWGVeZV1lcmV4ZYJlg4uKZZtln2WrZbdlw2XGZcFlxGXMZdJl22XZZeBl4WXxZ3JmCmYDZftnc2Y1ZjZmNGYcZk9mRGZJZkFmXmZdZmRmZ2Zo" + + "Zl9mYmZwZoNmiGaOZolmhGaYZp1mwWa5Zslmvma8////////ZsRmuGbWZtpm4GY/ZuZm6WbwZvVm92cPZxZnHmcmZyeXOGcuZz9nNmdBZzhnN2dGZ15nYGdZZ2NnZGeJZ3BnqWd8Z2pnjGeLZ6ZnoWeFZ7dn72e0Z+xns2fpZ7hn5GfeZ91n4mfuZ7lnzmfGZ+dqnGge" + + "aEZoKWhAaE1oMmhO//9os2graFloY2h3aH9on2iPaK1olGidaJtog2quaLlodGi1aKBoumkPaI1ofmkBaMppCGjYaSJpJmjhaQxozWjUaOdo1Wk2aRJpBGjXaONpJWj5aOBo72koaSppGmkjaSFoxml5aXdpXGl4aWtpVGl+aW5pOWl0aT1pWWkwaWFpXmldaYFpammy" + + "aa5p0Gm/acFp02m+ac5b6GnKad1pu2nDaadqLmmRaaBpnGmVabRp3mnoagJqG2n/awpp+WnyaedqBWmxah5p7WoUaetqCmoSasFqI2oTakRqDGpyajZqeGpHamJqWWpmakhqOGoiapBqjWqgaoRqomqj////////apeGF2q7asNqwmq4arNqrGreatFq32qqatpq6mr7" + + "awWGFmr6axJrFpsxax9rOGs3dtxrOZjua0drQ2tJa1BrWWtUa1trX2tha3hreWt/a4BrhGuDa41rmGuVa55rpGuqa6trr2uya7Frs2u3a7xrxmvLa9Nr32vsa+tr82vv//+evmwIbBNsFGwbbCRsI2xebFVsYmxqbIJsjWyabIFsm2x+bGhsc2ySbJBsxGzxbNNsvWzX" + + "bMVs3WyubLFsvmy6bNts72zZbOptH4hNbTZtK209bThtGW01bTNtEm0MbWNtk21kbVpteW1ZbY5tlW/kbYVt+W4VbgpttW3HbeZtuG3Gbext3m3Mbeht0m3Fbfpt2W3kbdVt6m3ubi1ubm4ubhlucm5fbj5uI25rbitudm5Nbh9uQ246bk5uJG7/bh1uOG6CbqpumG7J" + + "brdu0269bq9uxG6ybtRu1W6PbqVuwm6fb0FvEXBMbuxu+G7+bz9u8m8xbu9vMm7M////////bz5vE273b4Zvem94b4FvgG9vb1tv829tb4JvfG9Yb45vkW/Cb2Zvs2+jb6FvpG+5b8Zvqm/fb9Vv7G/Ub9hv8W/ub9twCXALb/pwEXABcA9v/nAbcBpvdHAdcBhwH3Aw" + + "cD5wMnBRcGNwmXCScK9w8XCscLhws3CucN9wy3Dd//9w2XEJcP1xHHEZcWVxVXGIcWZxYnFMcVZxbHGPcftxhHGVcahxrHHXcblxvnHScclx1HHOceBx7HHncfVx/HH5cf9yDXIQchtyKHItcixyMHIycjtyPHI/ckByRnJLclhydHJ+coJygXKHcpJylnKicqdyuXKy" + + "csNyxnLEcs5y0nLicuBy4XL5cvdQD3MXcwpzHHMWcx1zNHMvcylzJXM+c05zT57Yc1dzanNoc3BzeHN1c3tzenPIc7NzznO7c8Bz5XPuc950onQFdG90JXP4dDJ0OnRVdD90X3RZdEF0XHRpdHB0Y3RqdHZ0fnSLdJ50p3TKdM901HPx////////dOB043TndOl07nTy" + + "dPB08XT4dPd1BHUDdQV1DHUOdQ11FXUTdR51JnUsdTx1RHVNdUp1SXVbdUZ1WnVpdWR1Z3VrdW11eHV2dYZ1h3V0dYp1iXWCdZR1mnWddaV1o3XCdbN1w3W1db11uHW8dbF1zXXKddJ12XXjdd51/nX///91/HYBdfB1+nXydfN2C3YNdgl2H3YndiB2IXYidiR2NHYw" + + "djt2R3ZIdkZ2XHZYdmF2YnZodml2anZndmx2cHZydnZ2eHZ8doB2g3aIdot2jnaWdpN2mXaadrB2tHa4drl2unbCds121nbSdt524Xbldud26oYvdvt3CHcHdwR3KXckdx53JXcmdxt3N3c4d0d3Wndod2t3W3dld393fnd5d453i3eRd6B3nnewd7Z3uXe/d7x3vXe7" + + "d8d3zXfXd9p33Hfjd+53/HgMeBJ5JnggeSp4RXiOeHR4hnh8eJp4jHijeLV4qniveNF4xnjLeNR4vni8eMV4ynjs////////eOd42nj9ePR5B3kSeRF5GXkseSt5QHlgeVd5X3laeVV5U3l6eX95inmdeaefS3mqea55s3m5ebp5yXnVeed57HnheeN6CHoNehh6GXog" + + "eh95gHoxejt6Pno3ekN6V3pJemF6Ynppn516cHp5en16iHqXepV6mHqWeql6yHqw//96tnrFesR6v5CDesd6ynrNes961XrTetl62nrdeuF64nrmeu168HsCew97CnsGezN7GHsZex57NXsoezZ7UHt6ewR7TXsLe0x7RXt1e2V7dHtne3B7cXtse257nXuYe597jXuc" + + "e5p7i3uSe497XXuZe8t7wXvMe897tHvGe9176XwRfBR75nvlfGB8AHwHfBN783v3fBd8DXv2fCN8J3wqfB98N3wrfD18THxDfFR8T3xAfFB8WHxffGR8VnxlfGx8dXyDfJB8pHytfKJ8q3yhfKh8s3yyfLF8rny5fL18wHzFfMJ82HzSfNx84ps7fO988nz0fPZ8+n0G" + + "////////fQJ9HH0VfQp9RX1LfS59Mn0/fTV9Rn1zfVZ9Tn1yfWh9bn1PfWN9k32JfVt9j319fZt9un2ufaN9tX3Hfb19q349faJ9r33cfbh9n32wfdh93X3kfd59+33yfeF+BX4KfiN+IX4SfjF+H34Jfgt+In5GfmZ+O341fjl+Q343//9+Mn46fmd+XX5Wfl5+WX5a" + + "fnl+an5pfnx+e36DfdV+fY+ufn9+iH6Jfox+kn6QfpN+lH6Wfo5+m36cfzh/On9Ff0x/TX9Of1B/UX9Vf1R/WH9ff2B/aH9pf2d/eH+Cf4Z/g3+If4d/jH+Uf55/nX+af6N/r3+yf7l/rn+2f7iLcX/Ff8Z/yn/Vf9R/4X/mf+l/83/5mNyABoAEgAuAEoAYgBmAHIAh" + + "gCiAP4A7gEqARoBSgFiAWoBfgGKAaIBzgHKAcIB2gHmAfYB/gISAhoCFgJuAk4CagK1RkICsgNuA5YDZgN2AxIDagNaBCYDvgPGBG4EpgSOBL4FL////////louBRoE+gVOBUYD8gXGBboFlgWaBdIGDgYiBioGAgYKBoIGVgaSBo4FfgZOBqYGwgbWBvoG4gb2BwIHC" + + "gbqByYHNgdGB2YHYgciB2oHfgeCB54H6gfuB/oIBggKCBYIHggqCDYIQghaCKYIrgjiCM4JAglmCWIJdglqCX4Jk//+CYoJogmqCa4IugnGCd4J4gn6CjYKSgquCn4K7gqyC4YLjgt+C0oL0gvOC+oOTgwOC+4L5gt6DBoLcgwmC2YM1gzSDFoMygzGDQIM5g1CDRYMv" + + "gyuDF4MYg4WDmoOqg5+DooOWgyODjoOHg4qDfIO1g3ODdYOgg4mDqIP0hBOD64POg/2EA4PYhAuDwYP3hAeD4IPyhA2EIoQgg72EOIUGg/uEbYQqhDyFWoSEhHeEa4SthG6EgoRphEaELIRvhHmENYTKhGKEuYS/hJ+E2YTNhLuE2oTQhMGExoTWhKGFIYT/hPSFF4UY" + + "hSyFH4UVhRSE/IVAhWOFWIVI////////hUGGAoVLhVWFgIWkhYiFkYWKhaiFbYWUhZuF6oWHhZyFd4V+hZCFyYW6hc+FuYXQhdWF3YXlhdyF+YYKhhOGC4X+hfqGBoYihhqGMIY/hk1OVYZUhl+GZ4ZxhpOGo4aphqqGi4aMhraGr4bEhsaGsIbJiCOGq4bUht6G6Ybs" + + "//+G34bbhu+HEocGhwiHAIcDhvuHEYcJhw2G+YcKhzSHP4c3hzuHJYcphxqHYIdfh3iHTIdOh3SHV4doh26HWYdTh2OHaogFh6KHn4eCh6+Hy4e9h8CH0JbWh6uHxIezh8eHxoe7h++H8ofgiA+IDYf+h/aH94gOh9KIEYgWiBWIIoghiDGINog5iCeIO4hEiEKIUohZ" + + "iF6IYohriIGIfoieiHWIfYi1iHKIgoiXiJKIroiZiKKIjYikiLCIv4ixiMOIxIjUiNiI2YjdiPmJAoj8iPSI6IjyiQSJDIkKiROJQ4keiSWJKokriUGJRIk7iTaJOIlMiR2JYIle////////iWaJZIltiWqJb4l0iXeJfomDiYiJiomTiZiJoYmpiaaJrImvibKJuom9" + + "ib+JwInaidyJ3YnnifSJ+IoDihaKEIoMihuKHYolijaKQYpbilKKRopIinyKbYpsimKKhYqCioSKqIqhipGKpYqmipqKo4rEis2KworaiuuK84rn//+K5IrxixSK4IriiveK3orbiwyLB4saiuGLFosQixeLIIszl6uLJosriz6LKItBi0yLT4tOi0mLVotbi1qLa4tf" + + "i2yLb4t0i32LgIuMi46LkouTi5aLmYuajDqMQYw/jEiMTIxOjFCMVYxijGyMeIx6jIKMiYyFjIqMjYyOjJSMfIyYYh2MrYyqjL2MsoyzjK6MtozIjMGM5IzjjNqM/Yz6jPuNBI0FjQqNB40PjQ2NEJ9OjROMzY0UjRaNZ41tjXGNc42BjZmNwo2+jbqNz43ajdaNzI3b" + + "jcuN6o3rjd+N4438jgiOCY3/jh2OHo4Qjh+OQo41jjCONI5K////////jkeOSY5MjlCOSI5ZjmSOYI4qjmOOVY52jnKOfI6BjoeOhY6EjouOio6TjpGOlI6ZjqqOoY6sjrCOxo6xjr6OxY7IjsuO247jjvyO+47rjv6PCo8FjxWPEo8ZjxOPHI8fjxuPDI8mjzOPO485" + + "j0WPQo8+j0yPSY9Gj06PV49c//+PYo9jj2SPnI+fj6OPrY+vj7eP2o/lj+KP6o/vkIeP9JAFj/mP+pARkBWQIZANkB6QFpALkCeQNpA1kDmP+JBPkFCQUZBSkA6QSZA+kFaQWJBekGiQb5B2lqiQcpCCkH2QgZCAkIqQiZCPkKiQr5CxkLWQ4pDkYkiQ25ECkRKRGZEy" + + "kTCRSpFWkViRY5FlkWmRc5FykYuRiZGCkaKRq5GvkaqRtZG0kbqRwJHBkcmRy5HQkdaR35HhkduR/JH1kfaSHpH/khSSLJIVkhGSXpJXkkWSSZJkkkiSlZI/kkuSUJKckpaSk5KbklqSz5K5kreS6ZMPkvqTRJMu////////kxmTIpMakyOTOpM1kzuTXJNgk3yTbpNW" + + "k7CTrJOtk5STuZPWk9eT6JPlk9iTw5Pdk9CTyJPklBqUFJQTlAOUB5QQlDaUK5Q1lCGUOpRBlFKURJRblGCUYpRelGqSKZRwlHWUd5R9lFqUfJR+lIGUf5WClYeVipWUlZaVmJWZ//+VoJWolaeVrZW8lbuVuZW+lcpv9pXDlc2VzJXVldSV1pXcleGV5ZXiliGWKJYu" + + "li+WQpZMlk+WS5Z3llyWXpZdll+WZpZylmyWjZaYlpWWl5aqlqeWsZaylrCWtJa2lriWuZbOlsuWyZbNiU2W3JcNltWW+ZcElwaXCJcTlw6XEZcPlxaXGZcklyqXMJc5lz2XPpdEl0aXSJdCl0mXXJdgl2SXZpdoUtKXa5dxl3mXhZd8l4GXepeGl4uXj5eQl5yXqJem" + + "l6OXs5e0l8OXxpfIl8uX3Jftn0+X8nrfl/aX9ZgPmAyYOJgkmCGYN5g9mEaYT5hLmGuYb5hw////////mHGYdJhzmKqYr5ixmLaYxJjDmMaY6ZjrmQOZCZkSmRSZGJkhmR2ZHpkkmSCZLJkumT2ZPplCmUmZRZlQmUuZUZlSmUyZVZmXmZiZpZmtma6ZvJnfmduZ3ZnY" + + "mdGZ7ZnumfGZ8pn7mfiaAZoPmgWZ4poZmiuaN5pFmkKaQJpD//+aPppVmk2aW5pXml+aYpplmmSaaZprmmqarZqwmryawJrPmtGa05rUmt6a35rimuOa5prvmuua7pr0mvGa95r7mwabGJsamx+bIpsjmyWbJ5somymbKpsumy+bMptEm0ObT5tNm06bUZtYm3Sbk5uD" + + "m5GblpuXm5+boJuom7SbwJvKm7mbxpvPm9Gb0pvjm+Kb5JvUm+GcOpvym/Gb8JwVnBScCZwTnAycBpwInBKcCpwEnC6cG5wlnCScIZwwnEecMpxGnD6cWpxgnGecdpx4nOec7JzwnQmdCJzrnQOdBp0qnSadr50jnR+dRJ0VnRKdQZ0/nT6dRp1I////////nV2dXp1k" + + "nVGdUJ1ZnXKdiZ2Hnaudb516nZqdpJ2pnbKdxJ3BnbuduJ26ncadz53Cndmd0534nead7Z3vnf2eGp4bnh6edZ55nn2egZ6InouejJ6SnpWekZ6dnqWeqZ64nqqerZdhnsyezp7PntCe1J7cnt6e3Z7gnuWe6J7v//+e9J72nvee+Z77nvye/Z8Hnwh2t58VnyGfLJ8+" + + "n0qfUp9Un2OfX59gn2GfZp9nn2yfap93n3Kfdp+Vn5yfoFgvaceQWXRkUdxxmf//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "/////////////////////////////////////////////w=="; + + + private static short[] UNICODE_TO_QR_KANJI = new short[1 << 16]; + + static { // Unpack the Shift JIS table into a more computation-friendly form + Arrays.fill(UNICODE_TO_QR_KANJI, (short)-1); + byte[] bytes = Base64.getDecoder().decode(PACKED_QR_KANJI_TO_UNICODE); + for (int i = 0; i < bytes.length; i += 2) { + char c = (char)(((bytes[i] & 0xFF) << 8) | (bytes[i + 1] & 0xFF)); + if (c == 0xFFFF) + continue; + assert UNICODE_TO_QR_KANJI[c] == -1; + UNICODE_TO_QR_KANJI[c] = (short)(i / 2); + } + } + + + + /*---- Miscellaneous ----*/ + + private QrSegmentAdvanced() {} // Not instantiable + +} From 2f4b0e8fd8749341c064bc73ab7d2f6fd4bf07a7 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Mon, 5 Nov 2018 04:37:27 +0000 Subject: [PATCH 55/85] Tweaked drawFormatBits() and drawDummyFormatBits() to use end-exclusive range in second copy for clarity. --- src/io/nayuki/fastqrcodegen/QrCode.java | 2 +- src/io/nayuki/fastqrcodegen/QrTemplate.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index dcd958f..091bec5 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -356,7 +356,7 @@ public final class QrCode { setModule(14 - i, 8, getBit(bits, i)); // Draw second copy - for (int i = 0; i <= 7; i++) + for (int i = 0; i < 8; i++) setModule(size - 1 - i, 8, getBit(bits, i)); for (int i = 8; i < 15; i++) setModule(8, size - 15 + i, getBit(bits, i)); diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 931d9b0..3ccfb40 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -149,7 +149,7 @@ final class QrTemplate { darkenFunctionModule(14 - i, 8, 0); // Draw second copy - for (int i = 0; i <= 7; i++) + for (int i = 0; i < 8; i++) darkenFunctionModule(size - 1 - i, 8, 0); for (int i = 8; i < 15; i++) darkenFunctionModule(8, size - 15 + i, 0); From df729db98bb9a4a7dbf1b18bb30891d634e820a4 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Mon, 5 Nov 2018 04:37:57 +0000 Subject: [PATCH 56/85] Added "data too long" exception class, changed code to make use of it, updated Javadoc comments. --- .../fastqrcodegen/DataTooLongException.java | 57 +++++++++++++++++++ src/io/nayuki/fastqrcodegen/QrCode.java | 19 ++++--- .../fastqrcodegen/QrCodeGeneratorWorker.java | 4 +- .../fastqrcodegen/QrSegmentAdvanced.java | 15 +++-- 4 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 src/io/nayuki/fastqrcodegen/DataTooLongException.java diff --git a/src/io/nayuki/fastqrcodegen/DataTooLongException.java b/src/io/nayuki/fastqrcodegen/DataTooLongException.java new file mode 100644 index 0000000..c6e4444 --- /dev/null +++ b/src/io/nayuki/fastqrcodegen/DataTooLongException.java @@ -0,0 +1,57 @@ +/* + * Fast QR Code generator library + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/fast-qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.fastqrcodegen; + + +/** + * Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include: + *
    + *
  • Decrease the error correction level if it was greater than {@code Ecc.LOW}.

  • + *
  • If the advanced {@code encodeSegments()} function with 6 arguments or the + * {@code makeSegmentsOptimally()} function was called, then increase the maxVersion argument + * if it was less than {@link QrCode#MAX_VERSION}. (This advice does not apply to the other + * factory functions because they search all versions up to {@code QrCode.MAX_VERSION}.)

  • + *
  • Split the text data into better or optimal segments in order to reduce the number of + * bits required. (See {@link QrSegmentAdvanced#makeSegmentsOptimally(String,QrCode.Ecc,int,int) + * QrSegmentAdvanced.makeSegmentsOptimally()}.)

  • + *
  • Change the text or binary data to be shorter.

  • + *
  • Change the text to fit the character set of a particular segment mode (e.g. alphanumeric).

  • + *
  • Propagate the error upward to the caller/user.

  • + *
+ * @see QrCode#encodeText(String, QrCode.Ecc) + * @see QrCode#encodeBinary(byte[], QrCode.Ecc) + * @see QrCode#encodeSegments(java.util.List, QrCode.Ecc) + * @see QrCode#encodeSegments(java.util.List, QrCode.Ecc, int, int, int, boolean) + * @see QrSegmentAdvanced#makeSegmentsOptimally(String, QrCode.Ecc, int, int) + */ +public class DataTooLongException extends IllegalArgumentException { + + public DataTooLongException() {} + + + public DataTooLongException(String msg) { + super(msg); + } + +} diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 091bec5..c24aae4 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -64,7 +64,7 @@ public final class QrCode { * @param ecl the error correction level to use (not {@code null}) (boostable) * @return a QR Code (not {@code null}) representing the text * @throws NullPointerException if the text or error correction level is {@code null} - * @throws IllegalArgumentException if the text fails to fit in the + * @throws DataTooLongException if the text fails to fit in the * largest version QR Code at the ECL, which means it is too long */ public static QrCode encodeText(String text, Ecc ecl) { @@ -84,7 +84,7 @@ public final class QrCode { * @param ecl the error correction level to use (not {@code null}) (boostable) * @return a QR Code (not {@code null}) representing the data * @throws NullPointerException if the data or error correction level is {@code null} - * @throws IllegalArgumentException if the data fails to fit in the + * @throws DataTooLongException if the data fails to fit in the * largest version QR Code at the ECL, which means it is too long */ public static QrCode encodeBinary(byte[] data, Ecc ecl) { @@ -109,7 +109,7 @@ public final class QrCode { * @param ecl the error correction level to use (not {@code null}) (boostable) * @return a QR Code (not {@code null}) representing the segments * @throws NullPointerException if the list of segments, any segment, or the error correction level is {@code null} - * @throws IllegalArgumentException if the segments fail to fit in the + * @throws DataTooLongException if the segments fail to fit in the * largest version QR Code at the ECL, which means they are too long */ public static QrCode encodeSegments(List segs, Ecc ecl) { @@ -137,8 +137,9 @@ public final class QrCode { * @return a QR Code (not {@code null}) representing the segments * @throws NullPointerException if the list of segments, any segment, or the error correction level is {@code null} * @throws IllegalArgumentException if 1 ≤ minVersion ≤ maxVersion ≤ 40 - * or −1 ≤ mask ≤ 7 is violated; or if the segments fail to - * fit in the maxVersion QR Code at the ECL, which means they are too long + * or −1 ≤ mask ≤ 7 is violated + * @throws DataTooLongException if the segments fail to fit in + * the maxVersion QR Code at the ECL, which means they are too long */ public static QrCode encodeSegments(List segs, Ecc ecl, int minVersion, int maxVersion, int mask, boolean boostEcl) { Objects.requireNonNull(segs); @@ -153,8 +154,12 @@ public final class QrCode { dataUsedBits = QrSegment.getTotalBits(segs, version); if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) break; // This version number is found to be suitable - if (version >= maxVersion) // All versions in the range could not fit the given data - throw new IllegalArgumentException("Data too long"); + if (version >= maxVersion) { // All versions in the range could not fit the given data + String msg = "Segment too long"; + if (dataUsedBits != -1) + msg = String.format("Data length = %d bits, Max capacity = %d bits", dataUsedBits, dataCapacityBits); + throw new DataTooLongException(msg); + } } assert dataUsedBits != -1; diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java index eb9dd36..fc0c9d1 100644 --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java @@ -91,9 +91,7 @@ public final class QrCodeGeneratorWorker { System.out.println(qr.getModule(x, y) ? 1 : 0); } - } catch (IllegalArgumentException e) { - if (!e.getMessage().equals("Data too long")) - throw e; + } catch (DataTooLongException e) { System.out.println(-1); } System.out.flush(); diff --git a/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java b/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java index 6cbdb7b..3cd5ae5 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java +++ b/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java @@ -57,8 +57,8 @@ public final class QrSegmentAdvanced { * @return a new mutable list (not {@code null}) of segments (not {@code null}) * containing the text, minimizing the bit length with respect to the constraints * @throws NullPointerException if the text or error correction level is {@code null} - * @throws IllegalArgumentException if 1 ≤ minVersion ≤ maxVersion ≤ 40 - * is violated, or if the data is too long to fit in a QR Code at maxVersion at ECL + * @throws IllegalArgumentException if 1 ≤ minVersion ≤ maxVersion ≤ 40 is violated + * @throws DataTooLongException if the text fails to fit in the maxVersion QR Code at the ECL */ public static List makeSegmentsOptimally(String text, QrCode.Ecc ecl, int minVersion, int maxVersion) { // Check arguments @@ -70,7 +70,7 @@ public final class QrSegmentAdvanced { // Iterate through version numbers, and make tentative segments List segs = null; int[] codePoints = toCodePoints(text); - for (int version = minVersion; version <= maxVersion; version++) { + for (int version = minVersion; ; version++) { if (version == minVersion || version == 10 || version == 27) segs = makeSegmentsOptimally(codePoints, version); assert segs != null; @@ -79,9 +79,14 @@ public final class QrSegmentAdvanced { int dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8; int dataUsedBits = QrSegment.getTotalBits(segs, version); if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) - return segs; + return segs; // This version number is found to be suitable + if (version >= maxVersion) { // All versions in the range could not fit the given text + String msg = "Segment too long"; + if (dataUsedBits != -1) + msg = String.format("Data length = %d bits, Max capacity = %d bits", dataUsedBits, dataCapacityBits); + throw new DataTooLongException(msg); + } } - throw new IllegalArgumentException("Data too long"); } From 8551314425dac9130a99c016d91360c72de63d7b Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Mon, 5 Nov 2018 04:38:11 +0000 Subject: [PATCH 57/85] Added/updated/deleted some comments. --- src/io/nayuki/fastqrcodegen/QrCode.java | 4 ++++ src/io/nayuki/fastqrcodegen/QrSegment.java | 6 +++--- src/io/nayuki/fastqrcodegen/QrTemplate.java | 5 ++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index c24aae4..32b2acf 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -197,6 +197,8 @@ public final class QrCode { /*---- Instance fields ----*/ + // Public immutable scalar parameters: + /** The version number of this QR Code, which is between 1 and 40 (inclusive). * This determines the size of this barcode. */ public final int version; @@ -213,6 +215,8 @@ public final class QrCode { * −1), the resulting object still has a mask value between 0 and 7. */ public final int mask; + // Private grid of modules/pixels: + // The modules of this QR Code. Immutable after constructor finishes. Accessed through getModule(). private final int[] modules; diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java index 86cce95..7ac0234 100644 --- a/src/io/nayuki/fastqrcodegen/QrSegment.java +++ b/src/io/nayuki/fastqrcodegen/QrSegment.java @@ -40,7 +40,8 @@ import java.util.Objects; *

This segment class imposes no length restrictions, but QR Codes have restrictions. * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. * Any segment longer than this is meaningless for the purpose of generating QR Codes. - * This class can represent kanji mode segments, but provides no help in encoding them.

+ * This class can represent kanji mode segments, but provides no help in encoding them + * - see {@link QrSegmentAdvanced} for full kanji support.

*/ public final class QrSegment { @@ -224,7 +225,7 @@ public final class QrSegment { * Always zero or positive. Not the same as the data's bit length. */ public final int numChars; - // The data bits of this segment. Not null. Accessed through getData(). + // The data bits of this segment. Not null. final int[] data; // Requires 0 <= bitLength <= data.length * 32. @@ -273,7 +274,6 @@ public final class QrSegment { } - /*---- Constants ----*/ static final int[] ALPHANUMERIC_MAP; diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 3ccfb40..f933bd7 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -90,7 +90,8 @@ final class QrTemplate { final int[][] masks; final int[] dataOutputBitIndexes; - private int[] isFunction; // Discarded when constructor finishes + // Indicates function modules that are not subjected to masking. Discarded when constructor finishes. + private int[] isFunction; private QrTemplate(int ver) { @@ -275,8 +276,6 @@ final class QrTemplate { } - /*---- Private static helper functions ----*/ - // Returns an ascending list of positions of alignment patterns for this version number. // Each position is in the range [0,177), and are used on both the x and y axes. // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. From b2671166cec80ba8deda8077e30e4fbfc43254af Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Mon, 5 Nov 2018 05:09:58 +0000 Subject: [PATCH 58/85] Renamed many variables in getPenaltyScore(), in preparation for future changes. --- src/io/nayuki/fastqrcodegen/QrCode.java | 52 ++++++++++++------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 32b2acf..b3bac2a 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -484,36 +484,36 @@ public final class QrCode { // Iterate over adjacent pairs of rows for (int index = 0, downIndex = size, end = size * size; index < end; ) { - int bits = 0; - int downBits = 0; - int runColor = 0; - int runLen = 0; + int curRow = 0; + int nextRow = 0; + int color = 0; + int runX = 0; for (int x = 0; x < size; x++, index++, downIndex++) { // Adjacent modules having same color - int bit = getBit(modules[index >>> 5], index); - if (bit != runColor) { - runColor = bit; - runLen = 1; + int c = getBit(modules[index >>> 5], index); + if (c != color) { + color = c; + runX = 1; } else { - runLen++; - if (runLen == 5) + runX++; + if (runX == 5) result += PENALTY_N1; - else if (runLen > 5) + else if (runX > 5) result++; } - black += bit; - bits = ((bits & 0b1111111111) << 1) | bit; + black += c; + curRow = ((curRow & 0b1111111111) << 1) | c; if (downIndex < end) { - downBits = ((downBits & 1) << 1) | getBit(modules[downIndex >>> 5], downIndex); + nextRow = ((nextRow & 1) << 1) | getBit(modules[downIndex >>> 5], downIndex); // 2*2 blocks of modules having same color - if (x >= 1 && (downBits == 0 || downBits == 3) && downBits == (bits & 3)) + if (x >= 1 && (nextRow == 0 || nextRow == 3) && nextRow == (curRow & 3)) result += PENALTY_N2; } // Finder-like pattern - if (x >= 10 && (bits == 0b00001011101 || bits == 0b10111010000)) + if (x >= 10 && (curRow == 0b00001011101 || curRow == 0b10111010000)) result += PENALTY_N3; } } @@ -521,25 +521,25 @@ public final class QrCode { // Iterate over single columns for (int x = 0; x < size; x++) { int bits = 0; - int runColor = 0; - int runLen = 0; + int color = 0; + int runY = 0; for (int y = 0, index = x; y < size; y++, index += size) { // Adjacent modules having same color - int bit = getBit(modules[index >>> 5], index); - if (bit != runColor) { - runColor = bit; - runLen = 1; + int c = getBit(modules[index >>> 5], index); + if (c != color) { + color = c; + runY = 1; } else { - runLen++; - if (runLen == 5) + runY++; + if (runY == 5) result += PENALTY_N1; - else if (runLen > 5) + else if (runY > 5) result++; } // Finder-like pattern - bits = ((bits & 0b1111111111) << 1) | bit; + bits = ((bits & 0b1111111111) << 1) | c; if (y >= 10 && (bits == 0b00001011101 || bits == 0b10111010000)) result += PENALTY_N3; } From f4f971f3847c6f9b9d498cd22e66ebbe850078d6 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Mon, 5 Nov 2018 05:10:33 +0000 Subject: [PATCH 59/85] Inverted some if-else statements in getPenaltyScore(). --- src/io/nayuki/fastqrcodegen/QrCode.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index b3bac2a..ccf474d 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -492,15 +492,15 @@ public final class QrCode { // Adjacent modules having same color int c = getBit(modules[index >>> 5], index); - if (c != color) { - color = c; - runX = 1; - } else { + if (c == color) { runX++; if (runX == 5) result += PENALTY_N1; else if (runX > 5) result++; + } else { + color = c; + runX = 1; } black += c; @@ -527,15 +527,15 @@ public final class QrCode { // Adjacent modules having same color int c = getBit(modules[index >>> 5], index); - if (c != color) { - color = c; - runY = 1; - } else { + if (c == color) { runY++; if (runY == 5) result += PENALTY_N1; else if (runY > 5) result++; + } else { + color = c; + runY = 1; } // Finder-like pattern From 655bb970ce1b2d658f3a7603532c8b78b0c0ccd2 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Mon, 5 Nov 2018 05:16:26 +0000 Subject: [PATCH 60/85] Completely rewrote the algorithm for detecting finder-like patterns, making it more accurate and compliant with the QR Code specification. --- src/io/nayuki/fastqrcodegen/QrCode.java | 52 ++++++++++++++++++------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index ccf474d..fe337ad 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -481,16 +481,16 @@ public final class QrCode { private int getPenaltyScore() { int result = 0; int black = 0; + int[] runHistory = new int[7]; // Iterate over adjacent pairs of rows for (int index = 0, downIndex = size, end = size * size; index < end; ) { int curRow = 0; int nextRow = 0; + Arrays.fill(runHistory, 0); int color = 0; int runX = 0; for (int x = 0; x < size; x++, index++, downIndex++) { - - // Adjacent modules having same color int c = getBit(modules[index >>> 5], index); if (c == color) { runX++; @@ -499,10 +499,12 @@ public final class QrCode { else if (runX > 5) result++; } else { + addRunToHistory(runX, runHistory); + if (color == 0 && hasFinderLikePattern(runHistory)) + result += PENALTY_N3; color = c; runX = 1; } - black += c; curRow = ((curRow & 0b1111111111) << 1) | c; if (downIndex < end) { @@ -511,21 +513,20 @@ public final class QrCode { if (x >= 1 && (nextRow == 0 || nextRow == 3) && nextRow == (curRow & 3)) result += PENALTY_N2; } - - // Finder-like pattern - if (x >= 10 && (curRow == 0b00001011101 || curRow == 0b10111010000)) - result += PENALTY_N3; } + addRunToHistory(runX, runHistory); + if (color == 1) + addRunToHistory(0, runHistory); // Dummy run of white + if (hasFinderLikePattern(runHistory)) + result += PENALTY_N3; } // Iterate over single columns for (int x = 0; x < size; x++) { - int bits = 0; + Arrays.fill(runHistory, 0); int color = 0; int runY = 0; for (int y = 0, index = x; y < size; y++, index += size) { - - // Adjacent modules having same color int c = getBit(modules[index >>> 5], index); if (c == color) { runY++; @@ -534,15 +535,18 @@ public final class QrCode { else if (runY > 5) result++; } else { + addRunToHistory(runY, runHistory); + if (color == 0 && hasFinderLikePattern(runHistory)) + result += PENALTY_N3; color = c; runY = 1; } - - // Finder-like pattern - bits = ((bits & 0b1111111111) << 1) | c; - if (y >= 10 && (bits == 0b00001011101 || bits == 0b10111010000)) - result += PENALTY_N3; } + addRunToHistory(runY, runHistory); + if (color == 1) + addRunToHistory(0, runHistory); // Dummy run of white + if (hasFinderLikePattern(runHistory)) + result += PENALTY_N3; } // Balance of black and white modules @@ -567,6 +571,24 @@ public final class QrCode { } + // Inserts the given value to the front of the given array, which shifts over the + // existing values and deletes the last value. A helper function for getPenaltyScore(). + private static void addRunToHistory(int run, int[] history) { + System.arraycopy(history, 0, history, 1, history.length - 1); + history[0] = run; + } + + + // Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and + // surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore(). + // Must only be called immediately after a run of white modules has ended. + private static boolean hasFinderLikePattern(int[] runHistory) { + int n = runHistory[1]; + return n > 0 && runHistory[2] == n && runHistory[4] == n && runHistory[5] == n + && runHistory[3] == n * 3 && Math.max(runHistory[0], runHistory[6]) >= n * 4; + } + + // Returns 0 or 1 based on the i'th bit of x. static int getBit(int x, int i) { return (x >>> i) & 1; From 47541e1b29df1aad8010d81af144fc1bc44fae72 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Mon, 5 Nov 2018 05:17:45 +0000 Subject: [PATCH 61/85] Simplified some code in getPenaltyScore(). --- src/io/nayuki/fastqrcodegen/QrCode.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index fe337ad..2fd362d 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -506,11 +506,11 @@ public final class QrCode { runX = 1; } black += c; - curRow = ((curRow & 0b1111111111) << 1) | c; if (downIndex < end) { - nextRow = ((nextRow & 1) << 1) | getBit(modules[downIndex >>> 5], downIndex); + curRow = ((curRow << 1) | c) & 3; + nextRow = ((nextRow << 1) | getBit(modules[downIndex >>> 5], downIndex)) & 3; // 2*2 blocks of modules having same color - if (x >= 1 && (nextRow == 0 || nextRow == 3) && nextRow == (curRow & 3)) + if (x >= 1 && (curRow == 0 || curRow == 3) && curRow == nextRow) result += PENALTY_N2; } } From df55fd6504a367f0648e573424b3d844e838ed86 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Mon, 5 Nov 2018 05:18:03 +0000 Subject: [PATCH 62/85] Rearranged variables, updated comment. --- src/io/nayuki/fastqrcodegen/QrCode.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 2fd362d..7825632 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -485,11 +485,11 @@ public final class QrCode { // Iterate over adjacent pairs of rows for (int index = 0, downIndex = size, end = size * size; index < end; ) { - int curRow = 0; - int nextRow = 0; Arrays.fill(runHistory, 0); int color = 0; int runX = 0; + int curRow = 0; + int nextRow = 0; for (int x = 0; x < size; x++, index++, downIndex++) { int c = getBit(modules[index >>> 5], index); if (c == color) { @@ -589,7 +589,7 @@ public final class QrCode { } - // Returns 0 or 1 based on the i'th bit of x. + // Returns 0 or 1 based on the (i mod 32)'th bit of x. static int getBit(int x, int i) { return (x >>> i) & 1; } From f8f01c4d4e930a77e2e237dbbaf3b2c627319de0 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 20 Jul 2019 19:12:00 +0000 Subject: [PATCH 63/85] Refactored logic in QrTemplate and ReedSolomonGenerator into new Memozier class. --- src/io/nayuki/fastqrcodegen/Memoizer.java | 88 +++++++++++++++++++ src/io/nayuki/fastqrcodegen/QrCode.java | 4 +- src/io/nayuki/fastqrcodegen/QrTemplate.java | 51 +---------- .../fastqrcodegen/ReedSolomonGenerator.java | 53 +---------- 4 files changed, 94 insertions(+), 102 deletions(-) create mode 100644 src/io/nayuki/fastqrcodegen/Memoizer.java diff --git a/src/io/nayuki/fastqrcodegen/Memoizer.java b/src/io/nayuki/fastqrcodegen/Memoizer.java new file mode 100644 index 0000000..cbb7455 --- /dev/null +++ b/src/io/nayuki/fastqrcodegen/Memoizer.java @@ -0,0 +1,88 @@ +/* + * Fast QR Code generator library + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/fast-qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.fastqrcodegen; + +import java.lang.ref.SoftReference; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + + +// A thread-safe cache based on soft references. +final class Memoizer { + + private final Function function; + private Map> cache = new HashMap<>(); + private Set pending = new HashSet<>(); + + + // Creates a memoizer based on the given function that takes one input to compute an output. + public Memoizer(Function func) { + function = func; + } + + + // Computes function.apply(arg) or returns a cached copy of a previous call. + public R get(T arg) { + while (true) { + synchronized(this) { + if (cache.containsKey(arg)) { + SoftReference ref = cache.get(arg); + R result = ref.get(); + if (result != null) + return result; + cache.remove(arg); + } + // Now cache.containsKey(arg) == false + + if (!pending.contains(arg)) { + pending.add(arg); + break; + } + + try { + this.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + try { + R result = function.apply(arg); + synchronized(this) { + cache.put(arg, new SoftReference<>(result)); + } + return result; + } finally { + synchronized(this) { + pending.remove(arg); + this.notifyAll(); + } + } + } + +} diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 7825632..e25f377 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -248,7 +248,7 @@ public final class QrCode { errorCorrectionLevel = Objects.requireNonNull(ecl); Objects.requireNonNull(dataCodewords); - QrTemplate tpl = QrTemplate.getInstance(ver); + QrTemplate tpl = QrTemplate.MEMOIZER.get(ver); modules = tpl.template.clone(); // Compute ECC, draw modules, do masking @@ -407,7 +407,7 @@ public final class QrCode { // Split data into blocks, calculate ECC, and interleave // (not concatenate) the bytes into a single sequence byte[] result = new byte[rawCodewords]; - ReedSolomonGenerator rs = ReedSolomonGenerator.getInstance(blockEccLen); + ReedSolomonGenerator rs = ReedSolomonGenerator.MEMOIZER.get(blockEccLen); byte[] ecc = new byte[blockEccLen]; // Temporary storage per iteration for (int i = 0, k = 0; i < numBlocks; i++) { int datLen = shortBlockDataLen + (i < numShortBlocks ? 0 : 1); diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index f933bd7..b2e435a 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -25,59 +25,12 @@ package io.nayuki.fastqrcodegen; import static io.nayuki.fastqrcodegen.QrCode.MAX_VERSION; import static io.nayuki.fastqrcodegen.QrCode.MIN_VERSION; -import java.lang.ref.SoftReference; final class QrTemplate { - /*---- Factory members ----*/ - - public static QrTemplate getInstance(int version) { - if (version < MIN_VERSION || version > MAX_VERSION) - throw new IllegalArgumentException("Version out of range"); - - while (true) { - synchronized(cache) { - SoftReference ref = cache[version]; - if (ref != null) { - QrTemplate result = ref.get(); - if (result != null) - return result; - cache[version] = null; - } - - if (!isPending[version]) { - isPending[version] = true; - break; - } - - try { - cache.wait(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - try { - QrTemplate tpl = new QrTemplate(version); - synchronized(cache) { - cache[version] = new SoftReference<>(tpl); - } - return tpl; - } finally { - synchronized(cache) { - isPending[version] = false; - cache.notifyAll(); - } - } - } - - - @SuppressWarnings("unchecked") - private static final SoftReference[] cache = new SoftReference[MAX_VERSION + 1]; - - private static final boolean[] isPending = new boolean[MAX_VERSION + 1]; + public static final Memoizer MEMOIZER + = new Memoizer<>(QrTemplate::new); diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java index aef45c0..697bc81 100644 --- a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -23,63 +23,14 @@ package io.nayuki.fastqrcodegen; -import java.lang.ref.SoftReference; import java.util.Arrays; import java.util.Objects; final class ReedSolomonGenerator { - /*---- Factory members ----*/ - - public static ReedSolomonGenerator getInstance(int degree) { - if (degree < 1 || degree > MAX_DEGREE) - throw new IllegalArgumentException("Degree out of range"); - - while (true) { - synchronized(cache) { - SoftReference ref = cache[degree]; - if (ref != null) { - ReedSolomonGenerator result = ref.get(); - if (result != null) - return result; - cache[degree] = null; - } - - if (!isPending[degree]) { - isPending[degree] = true; - break; - } - - try { - cache.wait(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - try { - ReedSolomonGenerator rs = new ReedSolomonGenerator(degree); - synchronized(cache) { - cache[degree] = new SoftReference<>(rs); - } - return rs; - } finally { - synchronized(cache) { - isPending[degree] = false; - cache.notifyAll(); - } - } - } - - - private static final int MAX_DEGREE = 30; - - @SuppressWarnings("unchecked") - private static final SoftReference[] cache = new SoftReference[MAX_DEGREE + 1]; - - private static final boolean[] isPending = new boolean[MAX_DEGREE + 1]; + public static final Memoizer MEMOIZER + = new Memoizer<>(ReedSolomonGenerator::new); From fa7a0926729a297cce99e670e12fc1b1449fb26f Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 20 Jul 2019 19:13:19 +0000 Subject: [PATCH 64/85] Deleted some section comments. --- src/io/nayuki/fastqrcodegen/QrTemplate.java | 3 --- src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java | 6 ------ 2 files changed, 9 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index b2e435a..c8da8d9 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -33,9 +33,6 @@ final class QrTemplate { = new Memoizer<>(QrTemplate::new); - - /*---- Instance members ----*/ - private final int version; private final int size; diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java index 697bc81..971aa02 100644 --- a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -33,9 +33,6 @@ final class ReedSolomonGenerator { = new Memoizer<>(ReedSolomonGenerator::new); - - /*---- Instance members ----*/ - // 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, // stored from highest to lowest power, excluding the leading term which is always 1. @@ -90,9 +87,6 @@ final class ReedSolomonGenerator { } - - /*---- Constant members ----*/ - // 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) { From 960b9cd32dd9c8fb8580f57c5cdee0f6aa1b181b Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 20 Jul 2019 19:13:46 +0000 Subject: [PATCH 65/85] Deleted some static imports. --- src/io/nayuki/fastqrcodegen/QrTemplate.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index c8da8d9..98cdeb7 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -23,9 +23,6 @@ package io.nayuki.fastqrcodegen; -import static io.nayuki.fastqrcodegen.QrCode.MAX_VERSION; -import static io.nayuki.fastqrcodegen.QrCode.MIN_VERSION; - final class QrTemplate { @@ -45,7 +42,7 @@ final class QrTemplate { private QrTemplate(int ver) { - if (ver < MIN_VERSION || ver > MAX_VERSION) + if (ver < QrCode.MIN_VERSION || ver > QrCode.MAX_VERSION) throw new IllegalArgumentException("Version out of range"); version = ver; size = version * 4 + 17; @@ -249,7 +246,7 @@ final class QrTemplate { // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. static int getNumRawDataModules(int ver) { - if (ver < MIN_VERSION || ver > MAX_VERSION) + if (ver < QrCode.MIN_VERSION || ver > QrCode.MAX_VERSION) throw new IllegalArgumentException("Version number out of range"); int result = (16 * ver + 128) * ver + 64; if (ver >= 2) { From 6d6e0f3fded0d5ac0f0e69fd2e8a759c340873ab Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 20 Jul 2019 23:54:55 +0000 Subject: [PATCH 66/85] 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. --- src/io/nayuki/fastqrcodegen/BitBuffer.java | 45 +++++-------------- src/io/nayuki/fastqrcodegen/QrCode.java | 13 +++--- src/io/nayuki/fastqrcodegen/QrTemplate.java | 21 ++++++--- .../fastqrcodegen/ReedSolomonGenerator.java | 18 ++++---- 4 files changed, 42 insertions(+), 55 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java index 18e1027..fafafd5 100644 --- a/src/io/nayuki/fastqrcodegen/BitBuffer.java +++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java @@ -27,24 +27,20 @@ import java.util.Arrays; import java.util.Objects; -/** - * An appendable sequence of bits (0s and 1s). Mainly used by {@link QrSegment}. - */ +// An appendable sequence of bits (0s and 1s), mainly used by QrSegment. final class BitBuffer { /*---- Fields ----*/ - int[] data; + int[] data; // In each 32-bit word, bits are filled from top down. - int bitLength; + int bitLength; // Always non-negative. /*---- Constructor ----*/ - /** - * Constructs an empty bit buffer (length 0). - */ + // Creates an empty bit buffer. public BitBuffer() { data = new int[64]; bitLength = 0; @@ -54,10 +50,7 @@ final class BitBuffer { /*---- Methods ----*/ - /** - * Returns the length of this sequence, which is a non-negative value. - * @return the length of this sequence - */ + // Returns the bit at the given index, yielding 0 or 1. public int getBit(int index) { if (index < 0 || index >= bitLength) throw new IndexOutOfBoundsException(); @@ -65,11 +58,8 @@ final class BitBuffer { } - /** - * Returns an array representing this buffer's bits packed into bytes - * 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 - */ + // Returns a new array representing this buffer's bits packed into + // bytes in big endian. The current bit length must be a multiple of 8. public byte[] getBytes() { if (bitLength % 8 != 0) throw new IllegalStateException("Data is not a whole number of bytes"); @@ -80,15 +70,8 @@ final class BitBuffer { } - /** - * Appends the specified number of low-order bits of the specified value to this - * buffer. Requires 0 ≤ len ≤ 31 and 0 ≤ val < 2len. - * @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 - */ + // Appends the given number of low-order bits of the given value + // to this buffer. Requires 0 <= len <= 31 and 0 <= val < 2^len. public void appendBits(int val, int len) { if (len < 0 || len > 31 || val >>> len != 0) throw new IllegalArgumentException("Value out of range"); @@ -114,14 +97,8 @@ final class BitBuffer { } - /** - * Appends the specified sequence of bits to this buffer. - * Requires 0 ≤ len ≤ 32 × 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 - */ + // Appends to this buffer the sequence of bits represented by the given + // word array and given bit length. Requires 0 <= len <= 32 * vals.length. public void appendBits(int[] vals, int len) { Objects.requireNonNull(vals); if (len == 0) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index e25f377..31c9a6e 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -215,9 +215,8 @@ public final class QrCode { * −1), the resulting object still has a mask value between 0 and 7. */ public final int mask; - // Private grid of modules/pixels: - - // The modules of this QR Code. Immutable after constructor finishes. Accessed through getModule(). + // Private grid of modules of this QR Code, packed tightly into bits. + // Immutable after constructor finishes. Accessed through getModule(). 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 - // data area of this QR Code symbol. Function modules need to be marked off before this is called. + // Draws the given sequence of 8-bit codewords (data and error correction) + // onto the entire data area of this QR Code, based on the given bit indexes. private void drawCodewords(int[] dataOutputBitIndexes, byte[] allCodewords) { Objects.requireNonNull(dataOutputBitIndexes); Objects.requireNonNull(allCodewords); @@ -443,7 +442,7 @@ public final class QrCode { // The function modules must be marked and the codeword bits must be drawn // 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 - // 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) { if (mask.length != modules.length) 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 - // 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. private int handleConstructorMasking(int[][] masks, int mask) { if (mask == -1) { // Automatically choose best mask diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 98cdeb7..b774cce 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -24,23 +24,28 @@ 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 { + // Use this memoizer to get instances of this class. public static final Memoizer MEMOIZER = new Memoizer<>(QrTemplate::new); - private final int version; - private final int size; + private final int version; // In the range [1, 40]. + private final int size; // Derived from version. - final int[] template; - final int[][] masks; - final int[] dataOutputBitIndexes; + final int[] template; // Length and values depend on version. + final int[][] masks; // masks.length == 8, and masks[i].length == template.length. + final int[] dataOutputBitIndexes; // Length and values depend on version. // 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; + // Creates a QR Code template for the given version number. private QrTemplate(int ver) { if (ver < QrCode.MIN_VERSION || ver > QrCode.MAX_VERSION) 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() { // Draw horizontal and vertical timing patterns for (int i = 0; i < size; i++) { @@ -101,7 +107,7 @@ final class QrTemplate { darkenFunctionModule(size - 1 - i, 8, 0); for (int i = 8; i < 15; i++) 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() { int[][] result = new int[8][template.length]; 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() { int[] result = new int[getNumRawDataModules(version) / 8 * 8]; 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) { assert 0 <= x && x < size; assert 0 <= y && y < size; diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java index 971aa02..cb2def7 100644 --- a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -27,29 +27,31 @@ import java.util.Arrays; import java.util.Objects; +// Computes Reed-Solomon error correction codewords for given data codewords. final class ReedSolomonGenerator { + // Use this memoizer to get instances of this class. public static final Memoizer MEMOIZER = new Memoizer<>(ReedSolomonGenerator::new); // 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, - // 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' is the temporary array computed in the constructor. private byte[][] polynomialMultiply; + // Creates a Reed-Solomon ECC generator polynomial for the given degree. private ReedSolomonGenerator(int degree) { if (degree < 1 || degree > 255) 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]; - 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}), - // 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). int root = 1; 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) { Objects.requireNonNull(data); Objects.requireNonNull(result); int degree = polynomialMultiply[0].length; assert result.length == degree; - // Compute the remainder by performing polynomial division 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]; for (int j = 0; j < degree - 1; j++) result[j] = (byte)(result[j + 1] ^ table[j]); From afc12dc9d22fa08ad91654c2023253247a238a87 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 20 Jul 2019 23:56:02 +0000 Subject: [PATCH 67/85] Synchronized some code with the parent project, without changing behavior. --- src/io/nayuki/fastqrcodegen/QrCode.java | 4 ++-- src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java | 1 - src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 31c9a6e..6d44c47 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -458,8 +458,8 @@ public final class QrCode { if (mask == -1) { // Automatically choose best mask int minPenalty = Integer.MAX_VALUE; for (int i = 0; i < 8; i++) { - drawFormatBits(i); applyMask(masks[i]); + drawFormatBits(i); int penalty = getPenaltyScore(); if (penalty < minPenalty) { mask = i; @@ -469,8 +469,8 @@ public final class QrCode { } } assert 0 <= mask && mask <= 7; - drawFormatBits(mask); // Overwrite old format bits applyMask(masks[mask]); // Apply the final choice of mask + drawFormatBits(mask); // Overwrite old format bits return mask; // The caller shall assign this value to the final-declared field } diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java index fc0c9d1..5bf9b30 100644 --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java @@ -81,7 +81,6 @@ public final class QrCodeGeneratorWorker { else segs = Arrays.asList(QrSegment.makeBytes(data)); - try { // Try to make QR Code symbol QrCode qr = QrCode.encodeSegments(segs, QrCode.Ecc.values()[errCorLvl], minVersion, maxVersion, mask, boostEcl != 0); // Print grid of modules diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java index cb2def7..a4b77be 100644 --- a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -92,7 +92,7 @@ final class ReedSolomonGenerator { // 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; + assert x >> 8 == 0 && y >> 8 == 0; // Russian peasant multiplication int z = 0; for (int i = 7; i >= 0; i--) { From 4671dca0ad45c3d0915e0c0cd9453da4afd51409 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 20 Jul 2019 23:56:18 +0000 Subject: [PATCH 68/85] Tweaked a bit of code for simplicity. --- src/io/nayuki/fastqrcodegen/QrCode.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 6d44c47..0093a27 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -377,13 +377,10 @@ public final class QrCode { private void setModule(int x, int y, int black) { assert 0 <= x && x < size; assert 0 <= y && y < size; + assert black == 0 || black == 1; int i = y * size + x; - if (black == 0) - modules[i >>> 5] &= ~(1 << i); - else if (black == 1) - modules[i >>> 5] |= 1 << i; - else - throw new IllegalArgumentException(); + modules[i >>> 5] &= ~(1 << i); + modules[i >>> 5] |= black << i; } From 42c357ae1c73633a73b26ac6de7f297e00a78956 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 21 Jul 2019 00:01:11 +0000 Subject: [PATCH 69/85] Renamed a local variable. --- src/io/nayuki/fastqrcodegen/QrCode.java | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 0093a27..1928014 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -482,13 +482,13 @@ public final class QrCode { // Iterate over adjacent pairs of rows for (int index = 0, downIndex = size, end = size * size; index < end; ) { Arrays.fill(runHistory, 0); - int color = 0; + int runColor = 0; int runX = 0; int curRow = 0; int nextRow = 0; for (int x = 0; x < size; x++, index++, downIndex++) { int c = getBit(modules[index >>> 5], index); - if (c == color) { + if (c == runColor) { runX++; if (runX == 5) result += PENALTY_N1; @@ -496,9 +496,9 @@ public final class QrCode { result++; } else { addRunToHistory(runX, runHistory); - if (color == 0 && hasFinderLikePattern(runHistory)) + if (runColor == 0 && hasFinderLikePattern(runHistory)) result += PENALTY_N3; - color = c; + runColor = c; runX = 1; } black += c; @@ -511,7 +511,7 @@ public final class QrCode { } } addRunToHistory(runX, runHistory); - if (color == 1) + if (runColor == 1) addRunToHistory(0, runHistory); // Dummy run of white if (hasFinderLikePattern(runHistory)) result += PENALTY_N3; @@ -520,11 +520,11 @@ public final class QrCode { // Iterate over single columns for (int x = 0; x < size; x++) { Arrays.fill(runHistory, 0); - int color = 0; + int runColor = 0; int runY = 0; for (int y = 0, index = x; y < size; y++, index += size) { int c = getBit(modules[index >>> 5], index); - if (c == color) { + if (c == runColor) { runY++; if (runY == 5) result += PENALTY_N1; @@ -532,14 +532,14 @@ public final class QrCode { result++; } else { addRunToHistory(runY, runHistory); - if (color == 0 && hasFinderLikePattern(runHistory)) + if (runColor == 0 && hasFinderLikePattern(runHistory)) result += PENALTY_N3; - color = c; + runColor = c; runY = 1; } } addRunToHistory(runY, runHistory); - if (color == 1) + if (runColor == 1) addRunToHistory(0, runHistory); // Dummy run of white if (hasFinderLikePattern(runHistory)) result += PENALTY_N3; From f05a8f9098bd1bbad12b04bf87d783bb4f192c99 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 21 Jul 2019 00:04:52 +0000 Subject: [PATCH 70/85] Replaced the finder-like pattern detection algorithm with a more sophisticated and accurate one, synchronizing with the parent project. --- src/io/nayuki/fastqrcodegen/QrCode.java | 68 ++++++++++++++----------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 1928014..b629391 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -481,9 +481,10 @@ public final class QrCode { // Iterate over adjacent pairs of rows for (int index = 0, downIndex = size, end = size * size; index < end; ) { - Arrays.fill(runHistory, 0); int runColor = 0; int runX = 0; + Arrays.fill(runHistory, 0); + int padRun = size; // Add white border to initial run int curRow = 0; int nextRow = 0; for (int x = 0; x < size; x++, index++, downIndex++) { @@ -495,9 +496,10 @@ public final class QrCode { else if (runX > 5) result++; } else { - addRunToHistory(runX, runHistory); - if (runColor == 0 && hasFinderLikePattern(runHistory)) - result += PENALTY_N3; + finderPenaltyAddHistory(runX + padRun, runHistory); + padRun = 0; + if (runColor == 0) + result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; runColor = c; runX = 1; } @@ -510,18 +512,15 @@ public final class QrCode { result += PENALTY_N2; } } - addRunToHistory(runX, runHistory); - if (runColor == 1) - addRunToHistory(0, runHistory); // Dummy run of white - if (hasFinderLikePattern(runHistory)) - result += PENALTY_N3; + result += finderPenaltyTerminateAndCount(runColor, runX + padRun, runHistory) * PENALTY_N3; } // Iterate over single columns for (int x = 0; x < size; x++) { - Arrays.fill(runHistory, 0); int runColor = 0; int runY = 0; + Arrays.fill(runHistory, 0); + int padRun = size; // Add white border to initial run for (int y = 0, index = x; y < size; y++, index += size) { int c = getBit(modules[index >>> 5], index); if (c == runColor) { @@ -531,18 +530,15 @@ public final class QrCode { else if (runY > 5) result++; } else { - addRunToHistory(runY, runHistory); - if (runColor == 0 && hasFinderLikePattern(runHistory)) - result += PENALTY_N3; + finderPenaltyAddHistory(runY + padRun, runHistory); + padRun = 0; + if (runColor == 0) + result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; runColor = c; runY = 1; } } - addRunToHistory(runY, runHistory); - if (runColor == 1) - addRunToHistory(0, runHistory); // Dummy run of white - if (hasFinderLikePattern(runHistory)) - result += PENALTY_N3; + result += finderPenaltyTerminateAndCount(runColor, runY + padRun, runHistory) * PENALTY_N3; } // Balance of black and white modules @@ -567,21 +563,33 @@ public final class QrCode { } - // Inserts the given value to the front of the given array, which shifts over the - // existing values and deletes the last value. A helper function for getPenaltyScore(). - private static void addRunToHistory(int run, int[] history) { - System.arraycopy(history, 0, history, 1, history.length - 1); - history[0] = run; + // 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(int[] runHistory) { + int n = runHistory[1]; + assert n <= size * 3; + boolean core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n; + return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) + + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0); + } + + + // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). + private int finderPenaltyTerminateAndCount(int currentRunColor, int currentRunLength, int[] runHistory) { + if (currentRunColor == 1) { // Terminate black run + finderPenaltyAddHistory(currentRunLength, runHistory); + currentRunLength = 0; + } + currentRunLength += size; // Add white border to final run + finderPenaltyAddHistory(currentRunLength, runHistory); + return finderPenaltyCountPatterns(runHistory); } - // Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and - // surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore(). - // Must only be called immediately after a run of white modules has ended. - private static boolean hasFinderLikePattern(int[] runHistory) { - int n = runHistory[1]; - return n > 0 && runHistory[2] == n && runHistory[4] == n && runHistory[5] == n - && runHistory[3] == n * 3 && Math.max(runHistory[0], runHistory[6]) >= n * 4; + // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). + private static void finderPenaltyAddHistory(int currentRunLength, int[] runHistory) { + System.arraycopy(runHistory, 0, runHistory, 1, runHistory.length - 1); + runHistory[0] = currentRunLength; } From 1edf83109fabbbe6c6a2aa044bdd63540078c2eb Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 21 Jul 2019 17:18:03 +0000 Subject: [PATCH 71/85] Updated readme text, synchronized package-info Javadoc text with the readme. --- Readme.markdown | 7 ++++--- src/io/nayuki/fastqrcodegen/package-info.java | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Readme.markdown b/Readme.markdown index bd70143..1f83e40 100644 --- a/Readme.markdown +++ b/Readme.markdown @@ -5,9 +5,9 @@ Fast QR Code generator library Introduction ------------ -This Java library generates QR Code symbols, and its design is optimized for speed. It contrasts with another QR library by the same author which is slow but which optimizes for clarity and conciseness. The functionality of this library and its API are nearly identical to the slow library, but it runs anywhere from 1.5× to 6× as fast. +This Java library generates QR Code symbols, and its design is optimized for speed. It contrasts with another QR library by the same author which is slow but which optimizes for clarity and conciseness. The functionality of this library and its API are nearly identical to the slow library, but it runs anywhere from 1.5× to 10× as fast. -Home page for the fast library (design explanation, benchmarks): https://www.nayuki.io/page/fast-qr-code-generator-library +Home page for the fast library (design explanation, benchmarks): [https://www.nayuki.io/page/fast-qr-code-generator-library](https://www.nayuki.io/page/fast-qr-code-generator-library) Home page for the slow library (live demo, QR Code introduction, competitor comparisons): [https://www.nayuki.io/page/qr-code-generator-library](https://www.nayuki.io/page/qr-code-generator-library) @@ -22,6 +22,7 @@ Core features: * Encodes numeric and special-alphanumeric text in less space than general text * Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes * Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts +* Detects finder-like penalty patterns more accurately than other implementations * Open source code under the permissive MIT License Manual parameters: @@ -59,7 +60,7 @@ Examples License ------- -Copyright © 2018 Project Nayuki. (MIT License) +Copyright © 2019 Project Nayuki. (MIT License) [https://www.nayuki.io/page/fast-qr-code-generator-library](https://www.nayuki.io/page/fast-qr-code-generator-library) Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/src/io/nayuki/fastqrcodegen/package-info.java b/src/io/nayuki/fastqrcodegen/package-info.java index e70dc6c..50ddb0f 100644 --- a/src/io/nayuki/fastqrcodegen/package-info.java +++ b/src/io/nayuki/fastqrcodegen/package-info.java @@ -8,11 +8,12 @@ *

Features

*

Core features:

*
    - *
  • Available in 7 programming languages, all with nearly equal functionality: Java, JavaScript, TypeScript, Python, C++, C, Rust

  • - *
  • Significantly shorter code but more documentation comments compared to competing libraries

  • *
  • Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard

  • *
  • Output formats: Raw modules/pixels of the QR symbol, SVG XML string, {@code BufferedImage} raster bitmap

  • *
  • Encodes numeric and special-alphanumeric text in less space than general text

  • + *
  • Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes

  • + *
  • Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts

  • + *
  • Detects finder-like penalty patterns more accurately than other implementations

  • *
  • Open source code under the permissive MIT License

  • *
*

Manual parameters:

From 8dabf86641301b1de0fb91e083fea5a6c529814f Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Mon, 14 Oct 2019 00:19:03 +0000 Subject: [PATCH 72/85] Renamed some method parameters to completely avoid variable shadowing. --- src/io/nayuki/fastqrcodegen/QrCode.java | 34 ++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index b629391..6f43d93 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -231,16 +231,16 @@ public final class QrCode { * @param ver the version number to use, which must be in the range 1 to 40 (inclusive) * @param ecl the error correction level to use * @param dataCodewords the bytes representing segments to encode (without ECC) - * @param mask the mask pattern to use, which is either −1 for automatic choice or from 0 to 7 for fixed choice + * @param msk the mask pattern to use, which is either −1 for automatic choice or from 0 to 7 for fixed choice * @throws NullPointerException if the byte array or error correction level is {@code null} * @throws IllegalArgumentException if the version or mask value is out of range, * or if the data is the wrong length for the specified version and error correction level */ - public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) { + public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int msk) { // Check arguments and initialize fields if (ver < MIN_VERSION || ver > MAX_VERSION) throw new IllegalArgumentException("Version value out of range"); - if (mask < -1 || mask > 7) + if (msk < -1 || msk > 7) throw new IllegalArgumentException("Mask value out of range"); version = ver; size = ver * 4 + 17; @@ -253,7 +253,7 @@ public final class QrCode { // Compute ECC, draw modules, do masking byte[] allCodewords = addEccAndInterleave(dataCodewords); drawCodewords(tpl.dataOutputBitIndexes, allCodewords); - this.mask = handleConstructorMasking(tpl.masks, mask); + this.mask = handleConstructorMasking(tpl.masks, msk); } @@ -345,9 +345,9 @@ public final class QrCode { // Draws two copies of the format bits (with its own error correction code) // based on the given mask and this object's error correction level field. - private void drawFormatBits(int mask) { + private void drawFormatBits(int msk) { // Calculate error correction code and pack bits - int data = errorCorrectionLevel.formatBits << 3 | mask; // errCorrLvl is uint2, mask is uint3 + int data = errorCorrectionLevel.formatBits << 3 | msk; // errCorrLvl is uint2, mask is uint3 int rem = data; for (int i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537); @@ -440,35 +440,35 @@ public final class QrCode { // 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 // QR Code needs exactly one (not zero, two, etc.) mask applied. - private void applyMask(int[] mask) { - if (mask.length != modules.length) + private void applyMask(int[] msk) { + if (msk.length != modules.length) throw new IllegalArgumentException(); - for (int i = 0; i < mask.length; i++) - modules[i] ^= mask[i]; + for (int i = 0; i < msk.length; i++) + modules[i] ^= msk[i]; } // A messy helper function for the constructor. This QR Code must be in an unmasked state when this // 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. - private int handleConstructorMasking(int[][] masks, int mask) { - if (mask == -1) { // Automatically choose best mask + private int handleConstructorMasking(int[][] masks, int msk) { + if (msk == -1) { // Automatically choose best mask int minPenalty = Integer.MAX_VALUE; for (int i = 0; i < 8; i++) { applyMask(masks[i]); drawFormatBits(i); int penalty = getPenaltyScore(); if (penalty < minPenalty) { - mask = i; + msk = i; minPenalty = penalty; } applyMask(masks[i]); // Undoes the mask due to XOR } } - assert 0 <= mask && mask <= 7; - applyMask(masks[mask]); // Apply the final choice of mask - drawFormatBits(mask); // Overwrite old format bits - return mask; // The caller shall assign this value to the final-declared field + assert 0 <= msk && msk <= 7; + applyMask(masks[msk]); // Apply the final choice of mask + drawFormatBits(msk); // Overwrite old format bits + return msk; // The caller shall assign this value to the final-declared field } From db2d52116a9e587785a80b1ba05825009db1fdfa Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 28 Jul 2021 18:45:28 +0000 Subject: [PATCH 73/85] Simplified QrCode.getPenalty(). --- src/io/nayuki/fastqrcodegen/QrCode.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 6f43d93..f7fa2ef 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -484,7 +484,6 @@ public final class QrCode { int runColor = 0; int runX = 0; Arrays.fill(runHistory, 0); - int padRun = size; // Add white border to initial run int curRow = 0; int nextRow = 0; for (int x = 0; x < size; x++, index++, downIndex++) { @@ -496,8 +495,7 @@ public final class QrCode { else if (runX > 5) result++; } else { - finderPenaltyAddHistory(runX + padRun, runHistory); - padRun = 0; + finderPenaltyAddHistory(runX, runHistory); if (runColor == 0) result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; runColor = c; @@ -512,7 +510,7 @@ public final class QrCode { result += PENALTY_N2; } } - result += finderPenaltyTerminateAndCount(runColor, runX + padRun, runHistory) * PENALTY_N3; + result += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3; } // Iterate over single columns @@ -520,7 +518,6 @@ public final class QrCode { int runColor = 0; int runY = 0; Arrays.fill(runHistory, 0); - int padRun = size; // Add white border to initial run for (int y = 0, index = x; y < size; y++, index += size) { int c = getBit(modules[index >>> 5], index); if (c == runColor) { @@ -530,15 +527,14 @@ public final class QrCode { else if (runY > 5) result++; } else { - finderPenaltyAddHistory(runY + padRun, runHistory); - padRun = 0; + finderPenaltyAddHistory(runY, runHistory); if (runColor == 0) result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; runColor = c; runY = 1; } } - result += finderPenaltyTerminateAndCount(runColor, runY + padRun, runHistory) * PENALTY_N3; + result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3; } // Balance of black and white modules @@ -587,7 +583,9 @@ public final class QrCode { // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). - private static void finderPenaltyAddHistory(int currentRunLength, int[] runHistory) { + private void finderPenaltyAddHistory(int currentRunLength, int[] runHistory) { + if (runHistory[0] == 0) + currentRunLength += size; // Add white border to initial run System.arraycopy(runHistory, 0, runHistory, 1, runHistory.length - 1); runHistory[0] = currentRunLength; } From 219d04a2477ea9c022c201c96d97bfbad8ac7586 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 28 Jul 2021 18:47:30 +0000 Subject: [PATCH 74/85] Simplified code to remove unnecessary `this`, also improving consistency with other field assignments, enabled by a local variable renaming in commit 8dabf8664130. --- src/io/nayuki/fastqrcodegen/QrCode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index f7fa2ef..afa8aaa 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -253,7 +253,7 @@ public final class QrCode { // Compute ECC, draw modules, do masking byte[] allCodewords = addEccAndInterleave(dataCodewords); drawCodewords(tpl.dataOutputBitIndexes, allCodewords); - this.mask = handleConstructorMasking(tpl.masks, msk); + mask = handleConstructorMasking(tpl.masks, msk); } From e4761348282ced2e90c7d67da43a255d852859f5 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 28 Jul 2021 18:50:30 +0000 Subject: [PATCH 75/85] Changed the words "white"->"light" and "black"->"dark" in comments and local variables, in order to match the vocabulary in the QR Code specification document. --- src/io/nayuki/fastqrcodegen/QrCode.java | 38 ++++++++++----------- src/io/nayuki/fastqrcodegen/QrTemplate.java | 4 +-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index afa8aaa..f6e801d 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -32,7 +32,7 @@ import java.util.Objects; /** * A QR Code symbol, which is a type of two-dimension barcode. * Invented by Denso Wave and described in the ISO/IEC 18004 standard. - *

Instances of this class represent an immutable square grid of black and white cells. + *

Instances of this class represent an immutable square grid of dark and light cells. * The class provides static factory functions to create a QR Code from text or binary data. * The class covers the QR Code Model 2 specification, supporting all versions (sizes) * from 1 to 40, all 4 error correction levels, and 4 character encoding modes.

@@ -262,12 +262,12 @@ public final class QrCode { /** * Returns the color of the module (pixel) at the specified coordinates, which is {@code false} - * for white or {@code true} for black. The top left corner has the coordinates (x=0, y=0). - * If the specified coordinates are out of bounds, then {@code false} (white) is returned. + * for light or {@code true} for dark. The top left corner has the coordinates (x=0, y=0). + * If the specified coordinates are out of bounds, then {@code false} (light) is returned. * @param x the x coordinate, where 0 is the left edge and size−1 is the right edge * @param y the y coordinate, where 0 is the top edge and size−1 is the bottom edge * @return {@code true} if the coordinates are in bounds and the module - * at that location is black, or {@code false} (white) otherwise + * at that location is dark, or {@code false} (light) otherwise */ public boolean getModule(int x, int y) { if (0 <= x && x < size && 0 <= y && y < size) { @@ -280,7 +280,7 @@ public final class QrCode { /** * Returns a raster image depicting this QR Code, with the specified module scale and border modules. - *

For example, toImage(scale=10, border=4) means to pad the QR Code with 4 white + *

For example, toImage(scale=10, border=4) means to pad the QR Code with 4 light * border modules on all four sides, and use 10×10 pixels to represent each module. * The resulting image only contains the hex colors 000000 and FFFFFF. * @param scale the side length (measured in pixels, must be positive) of each module @@ -368,19 +368,19 @@ public final class QrCode { setModule(size - 1 - i, 8, getBit(bits, i)); for (int i = 8; i < 15; i++) setModule(8, size - 15 + i, getBit(bits, i)); - setModule(8, size - 8, 1); // Always black + setModule(8, size - 8, 1); // Always dark } // Sets the module at the given coordinates to the given color. // Only used by the constructor. Coordinates must be in bounds. - private void setModule(int x, int y, int black) { + private void setModule(int x, int y, int dark) { assert 0 <= x && x < size; assert 0 <= y && y < size; - assert black == 0 || black == 1; + assert dark == 0 || dark == 1; int i = y * size + x; modules[i >>> 5] &= ~(1 << i); - modules[i >>> 5] |= black << i; + modules[i >>> 5] |= dark << i; } @@ -476,7 +476,7 @@ public final class QrCode { // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. private int getPenaltyScore() { int result = 0; - int black = 0; + int dark = 0; int[] runHistory = new int[7]; // Iterate over adjacent pairs of rows @@ -501,7 +501,7 @@ public final class QrCode { runColor = c; runX = 1; } - black += c; + dark += c; if (downIndex < end) { curRow = ((curRow << 1) | c) & 3; nextRow = ((nextRow << 1) | getBit(modules[downIndex >>> 5], downIndex)) & 3; @@ -537,10 +537,10 @@ public final class QrCode { result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3; } - // Balance of black and white modules - int total = size * size; // Note that size is odd, so black/total != 1/2 - // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)% - int k = (Math.abs(black * 20 - total * 10) + total - 1) / total - 1; + // Balance of dark and light modules + int total = size * size; // Note that size is odd, so dark/total != 1/2 + // Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)% + int k = (Math.abs(dark * 20 - total * 10) + total - 1) / total - 1; result += k * PENALTY_N4; return result; } @@ -559,7 +559,7 @@ public final class QrCode { } - // Can only be called immediately after a white run is added, and + // Can only be called immediately after a light run is added, and // returns either 0, 1, or 2. A helper function for getPenaltyScore(). private int finderPenaltyCountPatterns(int[] runHistory) { int n = runHistory[1]; @@ -572,11 +572,11 @@ public final class QrCode { // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). private int finderPenaltyTerminateAndCount(int currentRunColor, int currentRunLength, int[] runHistory) { - if (currentRunColor == 1) { // Terminate black run + if (currentRunColor == 1) { // Terminate dark run finderPenaltyAddHistory(currentRunLength, runHistory); currentRunLength = 0; } - currentRunLength += size; // Add white border to final run + currentRunLength += size; // Add light border to final run finderPenaltyAddHistory(currentRunLength, runHistory); return finderPenaltyCountPatterns(runHistory); } @@ -585,7 +585,7 @@ public final class QrCode { // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). private void finderPenaltyAddHistory(int currentRunLength, int[] runHistory) { if (runHistory[0] == 0) - currentRunLength += size; // Add white border to initial run + currentRunLength += size; // Add light border to initial run System.arraycopy(runHistory, 0, runHistory, 1, runHistory.length - 1); runHistory[0] = currentRunLength; } diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index b774cce..50ffc41 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -107,7 +107,7 @@ final class QrTemplate { darkenFunctionModule(size - 1 - i, 8, 0); for (int i = 8; i < 15; i++) darkenFunctionModule(8, size - 15 + i, 0); - darkenFunctionModule(8, size - 8, 1); // Always black + darkenFunctionModule(8, size - 8, 1); // Always dark } @@ -221,7 +221,7 @@ final class QrTemplate { // Marks the module at the given coordinates as a function module. - // Also either sets that module black or keeps its color unchanged. + // Also either sets that module dark or keeps its color unchanged. private void darkenFunctionModule(int x, int y, int enable) { assert 0 <= x && x < size; assert 0 <= y && y < size; From 2515a4213cce689f853f8cc9ca0d8ba78e796f5e Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 28 Jul 2021 19:00:32 +0000 Subject: [PATCH 76/85] Moved QrCode.toImage() out of the library and into the runnable demo program, slightly adapted some code, updated documentation comments. --- Readme.markdown | 4 +- src/io/nayuki/fastqrcodegen/QrCode.java | 29 -------- .../fastqrcodegen/QrCodeGeneratorDemo.java | 66 ++++++++++++++----- src/io/nayuki/fastqrcodegen/package-info.java | 4 +- 4 files changed, 53 insertions(+), 50 deletions(-) diff --git a/Readme.markdown b/Readme.markdown index 1f83e40..c14bc8a 100644 --- a/Readme.markdown +++ b/Readme.markdown @@ -18,7 +18,7 @@ Features Core features: * Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard -* Output formats: Raw modules/pixels of the QR symbol, SVG XML string, `BufferedImage` raster bitmap +* Output formats: Raw modules/pixels of the QR symbol, SVG XML string * Encodes numeric and special-alphanumeric text in less space than general text * Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes * Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts @@ -44,7 +44,7 @@ Examples // Simple operation QrCode qr0 = QrCode.encodeText("Hello, world!", QrCode.Ecc.MEDIUM); - BufferedImage img = qr0.toImage(4, 10); + BufferedImage img = toImage(qr0, 4, 10); // See QrCodeGeneratorDemo ImageIO.write(img, "png", new File("qr-code.png")); // Manual operation diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index f6e801d..66abd48 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -23,7 +23,6 @@ package io.nayuki.fastqrcodegen; -import java.awt.image.BufferedImage; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -278,34 +277,6 @@ public final class QrCode { } - /** - * Returns a raster image depicting this QR Code, with the specified module scale and border modules. - *

For example, toImage(scale=10, border=4) means to pad the QR Code with 4 light - * border modules on all four sides, and use 10×10 pixels to represent each module. - * The resulting image only contains the hex colors 000000 and FFFFFF. - * @param scale the side length (measured in pixels, must be positive) of each module - * @param border the number of border modules to add, which must be non-negative - * @return a new image representing this QR Code, with padding and scaling - * @throws IllegalArgumentException if the scale or border is out of range, or if - * {scale, border, size} cause the image dimensions to exceed Integer.MAX_VALUE - */ - public BufferedImage toImage(int scale, int border) { - if (scale <= 0 || border < 0) - throw new IllegalArgumentException("Value out of range"); - if (border > Integer.MAX_VALUE / 2 || size + border * 2L > Integer.MAX_VALUE / scale) - throw new IllegalArgumentException("Scale or border too large"); - - BufferedImage result = new BufferedImage((size + border * 2) * scale, (size + border * 2) * scale, BufferedImage.TYPE_INT_RGB); - for (int y = 0; y < result.getHeight(); y++) { - for (int x = 0; x < result.getWidth(); x++) { - boolean color = getModule(x / scale - border, y / scale - border); - result.setRGB(x, y, color ? 0x000000 : 0xFFFFFF); - } - } - return result; - } - - /** * Returns a string of SVG code for an image depicting this QR Code, with the specified number * of border modules. The string always uses Unix newlines (\n), regardless of the platform. diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java index f57836c..792b42d 100644 --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java @@ -33,6 +33,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Arrays; import java.util.List; +import java.util.Objects; import javax.imageio.ImageIO; @@ -57,7 +58,7 @@ public final class QrCodeGeneratorDemo { QrCode qr = QrCode.encodeText(text, errCorLvl); // Make the QR Code symbol - BufferedImage img = qr.toImage(10, 4); // Convert to bitmap image + BufferedImage img = toImage(qr, 10, 4); // Convert to bitmap image File imgFile = new File("hello-world-QR.png"); // File path for output ImageIO.write(img, "png", imgFile); // Write image to file @@ -74,15 +75,15 @@ public final class QrCodeGeneratorDemo { // Numeric mode encoding (3.33 bits per digit) qr = QrCode.encodeText("314159265358979323846264338327950288419716939937510", QrCode.Ecc.MEDIUM); - writePng(qr.toImage(13, 1), "pi-digits-QR.png"); + writePng(toImage(qr, 13, 1), "pi-digits-QR.png"); // Alphanumeric mode encoding (5.5 bits per character) qr = QrCode.encodeText("DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/", QrCode.Ecc.HIGH); - writePng(qr.toImage(10, 2), "alphanumeric-QR.png"); + writePng(toImage(qr, 10, 2), "alphanumeric-QR.png"); // Unicode text as UTF-8 qr = QrCode.encodeText("こんにちwa、世界! αβγδ", QrCode.Ecc.QUARTILE); - writePng(qr.toImage(10, 3), "unicode-QR.png"); + writePng(toImage(qr, 10, 3), "unicode-QR.png"); // Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland) qr = QrCode.encodeText( @@ -93,7 +94,7 @@ public final class QrCodeGeneratorDemo { + "for the hot day made her feel very sleepy and stupid), whether the pleasure of making a " + "daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly " + "a White Rabbit with pink eyes ran close by her.", QrCode.Ecc.HIGH); - writePng(qr.toImage(6, 10), "alice-wonderland-QR.png"); + writePng(toImage(qr, 6, 10), "alice-wonderland-QR.png"); } @@ -106,32 +107,32 @@ public final class QrCodeGeneratorDemo { String silver0 = "THE SQUARE ROOT OF 2 IS 1."; String silver1 = "41421356237309504880168872420969807856967187537694807317667973799"; qr = QrCode.encodeText(silver0 + silver1, QrCode.Ecc.LOW); - writePng(qr.toImage(10, 3), "sqrt2-monolithic-QR.png"); + writePng(toImage(qr, 10, 3), "sqrt2-monolithic-QR.png"); segs = Arrays.asList( QrSegment.makeAlphanumeric(silver0), QrSegment.makeNumeric(silver1)); qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); - writePng(qr.toImage(10, 3), "sqrt2-segmented-QR.png"); + writePng(toImage(qr, 10, 3), "sqrt2-segmented-QR.png"); // Illustration "golden" String golden0 = "Golden ratio φ = 1."; String golden1 = "6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911374"; String golden2 = "......"; qr = QrCode.encodeText(golden0 + golden1 + golden2, QrCode.Ecc.LOW); - writePng(qr.toImage(8, 5), "phi-monolithic-QR.png"); + writePng(toImage(qr, 8, 5), "phi-monolithic-QR.png"); segs = Arrays.asList( QrSegment.makeBytes(golden0.getBytes(StandardCharsets.UTF_8)), QrSegment.makeNumeric(golden1), QrSegment.makeAlphanumeric(golden2)); qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); - writePng(qr.toImage(8, 5), "phi-segmented-QR.png"); + writePng(toImage(qr, 8, 5), "phi-segmented-QR.png"); // Illustration "Madoka": kanji, kana, Cyrillic, full-width Latin, Greek characters String madoka = "「魔法少女まどか☆マギカ」って、 ИАИ desu κα?"; qr = QrCode.encodeText(madoka, QrCode.Ecc.LOW); - writePng(qr.toImage(9, 4), "madoka-utf8-QR.png"); + writePng(toImage(qr, 9, 4), "madoka-utf8-QR.png"); int[] kanjiChars = { // Kanji mode encoding (13 bits per character) 0x0035, 0x1002, 0x0FC0, 0x0AED, 0x0AD7, @@ -146,7 +147,7 @@ public final class QrCodeGeneratorDemo { bb.appendBits(c, 13); segs = Arrays.asList(new QrSegment(QrSegment.Mode.KANJI, kanjiChars.length, bb.data, bb.bitLength)); qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); - writePng(qr.toImage(9, 4), "madoka-kanji-QR.png"); + writePng(toImage(qr, 9, 4), "madoka-kanji-QR.png"); } @@ -158,26 +159,57 @@ public final class QrCodeGeneratorDemo { // Project Nayuki URL segs = QrSegment.makeSegments("https://www.nayuki.io/"); qr = QrCode.encodeSegments(segs, QrCode.Ecc.HIGH, QrCode.MIN_VERSION, QrCode.MAX_VERSION, -1, true); // Automatic mask - writePng(qr.toImage(8, 6), "project-nayuki-automask-QR.png"); + writePng(toImage(qr, 8, 6), "project-nayuki-automask-QR.png"); qr = QrCode.encodeSegments(segs, QrCode.Ecc.HIGH, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 3, true); // Force mask 3 - writePng(qr.toImage(8, 6), "project-nayuki-mask3-QR.png"); + writePng(toImage(qr, 8, 6), "project-nayuki-mask3-QR.png"); // Chinese text as UTF-8 segs = QrSegment.makeSegments("維基百科(Wikipedia,聆聽i/ˌwɪkᵻˈpiːdi.ə/)是一個自由內容、公開編輯且多語言的網路百科全書協作計畫"); qr = QrCode.encodeSegments(segs, QrCode.Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 0, true); // Force mask 0 - writePng(qr.toImage(10, 3), "unicode-mask0-QR.png"); + writePng(toImage(qr, 10, 3), "unicode-mask0-QR.png"); qr = QrCode.encodeSegments(segs, QrCode.Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 1, true); // Force mask 1 - writePng(qr.toImage(10, 3), "unicode-mask1-QR.png"); + writePng(toImage(qr, 10, 3), "unicode-mask1-QR.png"); qr = QrCode.encodeSegments(segs, QrCode.Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 5, true); // Force mask 5 - writePng(qr.toImage(10, 3), "unicode-mask5-QR.png"); + writePng(toImage(qr, 10, 3), "unicode-mask5-QR.png"); qr = QrCode.encodeSegments(segs, QrCode.Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 7, true); // Force mask 7 - writePng(qr.toImage(10, 3), "unicode-mask7-QR.png"); + writePng(toImage(qr, 10, 3), "unicode-mask7-QR.png"); } /*---- Utilities ----*/ + /** + * Returns a raster image depicting the specified QR Code, with the specified module scale and border modules. + *

For example, toImage(qr, scale=10, border=4) means to pad the QR Code with 4 light + * border modules on all four sides, and use 10×10 pixels to represent each module. + * The resulting image only contains the hex colors 000000 and FFFFFF. + * @param qr the QR Code to render (not {@code null}) + * @param scale the side length (measured in pixels, must be positive) of each module + * @param border the number of border modules to add, which must be non-negative + * @return a new image representing the QR Code, with padding and scaling + * @throws NullPointerException if the QR Code is {@code null} + * @throws IllegalArgumentException if the scale or border is out of range, or if + * {scale, border, size} cause the image dimensions to exceed Integer.MAX_VALUE + */ + private static BufferedImage toImage(QrCode qr, int scale, int border) { + Objects.requireNonNull(qr); + if (scale <= 0 || border < 0) + throw new IllegalArgumentException("Value out of range"); + if (border > Integer.MAX_VALUE / 2 || qr.size + border * 2L > Integer.MAX_VALUE / scale) + throw new IllegalArgumentException("Scale or border too large"); + + BufferedImage result = new BufferedImage((qr.size + border * 2) * scale, (qr.size + border * 2) * scale, BufferedImage.TYPE_INT_RGB); + for (int y = 0; y < result.getHeight(); y++) { + for (int x = 0; x < result.getWidth(); x++) { + boolean color = qr.getModule(x / scale - border, y / scale - border); + result.setRGB(x, y, color ? 0x000000 : 0xFFFFFF); + } + } + return result; + } + + // Helper function to reduce code duplication. private static void writePng(BufferedImage img, String filepath) throws IOException { ImageIO.write(img, "png", new File(filepath)); diff --git a/src/io/nayuki/fastqrcodegen/package-info.java b/src/io/nayuki/fastqrcodegen/package-info.java index 50ddb0f..220b8b2 100644 --- a/src/io/nayuki/fastqrcodegen/package-info.java +++ b/src/io/nayuki/fastqrcodegen/package-info.java @@ -9,7 +9,7 @@ *

Core features:

*
    *
  • Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard

  • - *
  • Output formats: Raw modules/pixels of the QR symbol, SVG XML string, {@code BufferedImage} raster bitmap

  • + *
  • Output formats: Raw modules/pixels of the QR symbol, SVG XML string

  • *
  • Encodes numeric and special-alphanumeric text in less space than general text

  • *
  • Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes

  • *
  • Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts

  • @@ -32,7 +32,7 @@ *import io.nayuki.fastqrcodegen.*; * *QrCode qr = QrCode.encodeText("Hello, world!", QrCode.Ecc.MEDIUM); - *BufferedImage img = qr.toImage(4, 10); + *BufferedImage img = toImage(qr, 4, 10); // See QrCodeGeneratorDemo *ImageIO.write(img, "png", new File("qr-code.png")); *

    Manual operation:

    *
    import java.util.List;
    
    From 1954c534bf59c133bc7481aad866390b622775fe Mon Sep 17 00:00:00 2001
    From: Project Nayuki 
    Date: Wed, 28 Jul 2021 19:02:39 +0000
    Subject: [PATCH 77/85] Moved QrCode.toSvgString() out of the library and into
     the runnable demo program, slightly adapted some code, updated documentation
     comments.
    
    ---
     Readme.markdown                               |  2 +-
     src/io/nayuki/fastqrcodegen/QrCode.java       | 34 ----------------
     .../fastqrcodegen/QrCodeGeneratorDemo.java    | 39 ++++++++++++++++++-
     src/io/nayuki/fastqrcodegen/package-info.java |  2 +-
     4 files changed, 40 insertions(+), 37 deletions(-)
    
    diff --git a/Readme.markdown b/Readme.markdown
    index c14bc8a..dfcff4c 100644
    --- a/Readme.markdown
    +++ b/Readme.markdown
    @@ -18,7 +18,7 @@ Features
     Core features:
     
     * Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard
    -* Output formats: Raw modules/pixels of the QR symbol, SVG XML string
    +* Output format: Raw modules/pixels of the QR symbol
     * Encodes numeric and special-alphanumeric text in less space than general text
     * Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes
     * Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts
    diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java
    index 66abd48..75e5e0f 100644
    --- a/src/io/nayuki/fastqrcodegen/QrCode.java
    +++ b/src/io/nayuki/fastqrcodegen/QrCode.java
    @@ -277,40 +277,6 @@ public final class QrCode {
     	}
     	
     	
    -	/**
    -	 * Returns a string of SVG code for an image depicting this QR Code, with the specified number
    -	 * of border modules. The string always uses Unix newlines (\n), regardless of the platform.
    -	 * @param border the number of border modules to add, which must be non-negative
    -	 * @return a string representing this QR Code as an SVG XML document
    -	 * @throws IllegalArgumentException if the border is negative
    -	 */
    -	public String toSvgString(int border) {
    -		if (border < 0)
    -			throw new IllegalArgumentException("Border must be non-negative");
    -		long brd = border;
    -		StringBuilder sb = new StringBuilder()
    -			.append("\n")
    -			.append("\n")
    -			.append(String.format("\n",
    -				size + brd * 2))
    -			.append("\t\n")
    -			.append("\t\n")
    -			.append("\n")
    -			.toString();
    -	}
    -	
    -	
     	
     	/*---- Private helper methods for constructor: Drawing function modules ----*/
     	
    diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java
    index 792b42d..5e9e2a0 100644
    --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java
    +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java
    @@ -62,7 +62,7 @@ public final class QrCodeGeneratorDemo {
     		File imgFile = new File("hello-world-QR.png");   // File path for output
     		ImageIO.write(img, "png", imgFile);              // Write image to file
     		
    -		String svg = qr.toSvgString(4);                  // Convert to SVG XML code
    +		String svg = toSvgString(qr, 4);                 // Convert to SVG XML code
     		File svgFile = new File("hello-world-QR.svg");   // File path for output
     		Files.write(svgFile.toPath(),                    // Write image to file
     			svg.getBytes(StandardCharsets.UTF_8));
    @@ -215,4 +215,41 @@ public final class QrCodeGeneratorDemo {
     		ImageIO.write(img, "png", new File(filepath));
     	}
     	
    +	
    +	/**
    +	 * Returns a string of SVG code for an image depicting the specified QR Code, with the specified
    +	 * number of border modules. The string always uses Unix newlines (\n), regardless of the platform.
    +	 * @param qr the QR Code to render (not {@code null})
    +	 * @param border the number of border modules to add, which must be non-negative
    +	 * @return a string representing the QR Code as an SVG XML document
    +	 * @throws NullPointerException if the QR Code is {@code null}
    +	 * @throws IllegalArgumentException if the border is negative
    +	 */
    +	private static String toSvgString(QrCode qr, int border) {
    +		Objects.requireNonNull(qr);
    +		if (border < 0)
    +			throw new IllegalArgumentException("Border must be non-negative");
    +		long brd = border;
    +		StringBuilder sb = new StringBuilder()
    +			.append("\n")
    +			.append("\n")
    +			.append(String.format("\n",
    +				qr.size + brd * 2))
    +			.append("\t\n")
    +			.append("\t\n")
    +			.append("\n")
    +			.toString();
    +	}
    +	
     }
    diff --git a/src/io/nayuki/fastqrcodegen/package-info.java b/src/io/nayuki/fastqrcodegen/package-info.java
    index 220b8b2..0ab23a6 100644
    --- a/src/io/nayuki/fastqrcodegen/package-info.java
    +++ b/src/io/nayuki/fastqrcodegen/package-info.java
    @@ -9,7 +9,7 @@
      * 

    Core features:

    *
      *
    • Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard

    • - *
    • Output formats: Raw modules/pixels of the QR symbol, SVG XML string

    • + *
    • Output format: Raw modules/pixels of the QR symbol

    • *
    • Encodes numeric and special-alphanumeric text in less space than general text

    • *
    • Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes

    • *
    • Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts

    • From 07725617d7b7302341fe1be6ba8c8609b91d5ae1 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 28 Jul 2021 19:03:31 +0000 Subject: [PATCH 78/85] Removed the test worker program, because this is not core functionality and is hard to explain. --- .../fastqrcodegen/QrCodeGeneratorWorker.java | 100 ------------------ 1 file changed, 100 deletions(-) delete mode 100644 src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java deleted file mode 100644 index 5bf9b30..0000000 --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * QR Code generator test worker - * - * This program reads data and encoding parameters from standard input and writes - * QR Code bitmaps to standard output. The I/O format is one integer per line. - * Run with no command line arguments. The program is intended for automated - * batch testing of end-to-end functionality of this QR Code generator library. - * - * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/page/fast-qr-code-generator-library - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - The Software is provided "as is", without warranty of any kind, express or - * implied, including but not limited to the warranties of merchantability, - * fitness for a particular purpose and noninfringement. In no event shall the - * authors or copyright holders be liable for any claim, damages or other - * liability, whether in an action of contract, tort or otherwise, arising from, - * out of or in connection with the Software or the use or other dealings in the - * Software. - */ - -package io.nayuki.fastqrcodegen; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; -import java.util.Scanner; - - -public final class QrCodeGeneratorWorker { - - public static void main(String[] args) { - // Set up input stream and start loop - try (Scanner input = new Scanner(System.in, "US-ASCII")) { - input.useDelimiter("\r\n|\n|\r"); - while (processCase(input)); - } - } - - - private static boolean processCase(Scanner input) { - // Read data length or exit - int length = input.nextInt(); - if (length == -1) - return false; - if (length > Short.MAX_VALUE) - throw new RuntimeException(); - - // Read data bytes - boolean isAscii = true; - byte[] data = new byte[length]; - for (int i = 0; i < data.length; i++) { - int b = input.nextInt(); - if (b < 0 || b > 255) - throw new RuntimeException(); - data[i] = (byte)b; - isAscii &= b < 128; - } - - // Read encoding parameters - int errCorLvl = input.nextInt(); - int minVersion = input.nextInt(); - int maxVersion = input.nextInt(); - int mask = input.nextInt(); - int boostEcl = input.nextInt(); - if (!(0 <= errCorLvl && errCorLvl <= 3) || !(-1 <= mask && mask <= 7) || (boostEcl >>> 1) != 0 - || !(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION)) - throw new RuntimeException(); - - // Make segments for encoding - List segs; - if (isAscii) - segs = QrSegment.makeSegments(new String(data, StandardCharsets.US_ASCII)); - else - segs = Arrays.asList(QrSegment.makeBytes(data)); - - try { // Try to make QR Code symbol - QrCode qr = QrCode.encodeSegments(segs, QrCode.Ecc.values()[errCorLvl], minVersion, maxVersion, mask, boostEcl != 0); - // Print grid of modules - System.out.println(qr.version); - for (int y = 0; y < qr.size; y++) { - for (int x = 0; x < qr.size; x++) - System.out.println(qr.getModule(x, y) ? 1 : 0); - } - - } catch (DataTooLongException e) { - System.out.println(-1); - } - System.out.flush(); - return true; - } - -} From 8640ddf8a53f81b72f7dfa08e2e764e86732f160 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 28 Jul 2021 19:04:14 +0000 Subject: [PATCH 79/85] Added spaces around multiplication operators in QrTemplate.getAlignmentPatternPositions(), for consistency with other code. --- src/io/nayuki/fastqrcodegen/QrTemplate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index 50ffc41..b841da2 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -241,7 +241,7 @@ final class QrTemplate { else { int numAlign = version / 7 + 2; int step = (version == 32) ? 26 : - (version*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2; + (version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2; int[] result = new int[numAlign]; result[0] = 6; for (int i = result.length - 1, pos = size - 7; i >= 1; i--, pos -= step) From 02d182ebc2c57968762bd612660dade7f0aedc1f Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 28 Jul 2021 19:09:20 +0000 Subject: [PATCH 80/85] Added parameters for custom module colors when rendering to BufferedImage, changed some demo code to use non-black/white colors. --- .../fastqrcodegen/QrCodeGeneratorDemo.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java index 5e9e2a0..33c2ff4 100644 --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java @@ -132,7 +132,7 @@ public final class QrCodeGeneratorDemo { // Illustration "Madoka": kanji, kana, Cyrillic, full-width Latin, Greek characters String madoka = "「魔法少女まどか☆マギカ」って、 ИАИ desu κα?"; qr = QrCode.encodeText(madoka, QrCode.Ecc.LOW); - writePng(toImage(qr, 9, 4), "madoka-utf8-QR.png"); + writePng(toImage(qr, 9, 4, 0xFFFFE0, 0x303080), "madoka-utf8-QR.png"); int[] kanjiChars = { // Kanji mode encoding (13 bits per character) 0x0035, 0x1002, 0x0FC0, 0x0AED, 0x0AD7, @@ -147,7 +147,7 @@ public final class QrCodeGeneratorDemo { bb.appendBits(c, 13); segs = Arrays.asList(new QrSegment(QrSegment.Mode.KANJI, kanjiChars.length, bb.data, bb.bitLength)); qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); - writePng(toImage(qr, 9, 4), "madoka-kanji-QR.png"); + writePng(toImage(qr, 9, 4, 0xE0F0FF, 0x404040), "madoka-kanji-QR.png"); } @@ -159,9 +159,9 @@ public final class QrCodeGeneratorDemo { // Project Nayuki URL segs = QrSegment.makeSegments("https://www.nayuki.io/"); qr = QrCode.encodeSegments(segs, QrCode.Ecc.HIGH, QrCode.MIN_VERSION, QrCode.MAX_VERSION, -1, true); // Automatic mask - writePng(toImage(qr, 8, 6), "project-nayuki-automask-QR.png"); + writePng(toImage(qr, 8, 6, 0xE0FFE0, 0x206020), "project-nayuki-automask-QR.png"); qr = QrCode.encodeSegments(segs, QrCode.Ecc.HIGH, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 3, true); // Force mask 3 - writePng(toImage(qr, 8, 6), "project-nayuki-mask3-QR.png"); + writePng(toImage(qr, 8, 6, 0xFFE0E0, 0x602020), "project-nayuki-mask3-QR.png"); // Chinese text as UTF-8 segs = QrSegment.makeSegments("維基百科(Wikipedia,聆聽i/ˌwɪkᵻˈpiːdi.ə/)是一個自由內容、公開編輯且多語言的網路百科全書協作計畫"); @@ -179,20 +179,27 @@ public final class QrCodeGeneratorDemo { /*---- Utilities ----*/ + private static BufferedImage toImage(QrCode qr, int scale, int border) { + return toImage(qr, scale, border, 0xFFFFFF, 0x000000); + } + + /** - * Returns a raster image depicting the specified QR Code, with the specified module scale and border modules. - *

      For example, toImage(qr, scale=10, border=4) means to pad the QR Code with 4 light - * border modules on all four sides, and use 10×10 pixels to represent each module. - * The resulting image only contains the hex colors 000000 and FFFFFF. + * Returns a raster image depicting the specified QR Code, with + * the specified module scale, border modules, and module colors. + *

      For example, scale=10 and border=4 means to pad the QR Code with 4 light border + * modules on all four sides, and use 10×10 pixels to represent each module. * @param qr the QR Code to render (not {@code null}) * @param scale the side length (measured in pixels, must be positive) of each module * @param border the number of border modules to add, which must be non-negative + * @param lightColor the color to use for light modules, in 0xRRGGBB format + * @param darkColor the color to use for dark modules, in 0xRRGGBB format * @return a new image representing the QR Code, with padding and scaling * @throws NullPointerException if the QR Code is {@code null} * @throws IllegalArgumentException if the scale or border is out of range, or if * {scale, border, size} cause the image dimensions to exceed Integer.MAX_VALUE */ - private static BufferedImage toImage(QrCode qr, int scale, int border) { + private static BufferedImage toImage(QrCode qr, int scale, int border, int lightColor, int darkColor) { Objects.requireNonNull(qr); if (scale <= 0 || border < 0) throw new IllegalArgumentException("Value out of range"); @@ -203,7 +210,7 @@ public final class QrCodeGeneratorDemo { for (int y = 0; y < result.getHeight(); y++) { for (int x = 0; x < result.getWidth(); x++) { boolean color = qr.getModule(x / scale - border, y / scale - border); - result.setRGB(x, y, color ? 0x000000 : 0xFFFFFF); + result.setRGB(x, y, color ? darkColor : lightColor); } } return result; From bff4ea078c0434416ea07155dc5dde3acfc40630 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 28 Jul 2021 19:12:53 +0000 Subject: [PATCH 81/85] Added parameters for custom module colors when rendering to SVG. --- .../fastqrcodegen/QrCodeGeneratorDemo.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java index 33c2ff4..3f76d83 100644 --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java @@ -62,9 +62,9 @@ public final class QrCodeGeneratorDemo { File imgFile = new File("hello-world-QR.png"); // File path for output ImageIO.write(img, "png", imgFile); // Write image to file - String svg = toSvgString(qr, 4); // Convert to SVG XML code - File svgFile = new File("hello-world-QR.svg"); // File path for output - Files.write(svgFile.toPath(), // Write image to file + String svg = toSvgString(qr, 4, "#FFFFFF", "#000000"); // Convert to SVG XML code + File svgFile = new File("hello-world-QR.svg"); // File path for output + Files.write(svgFile.toPath(), // Write image to file svg.getBytes(StandardCharsets.UTF_8)); } @@ -228,12 +228,16 @@ public final class QrCodeGeneratorDemo { * number of border modules. The string always uses Unix newlines (\n), regardless of the platform. * @param qr the QR Code to render (not {@code null}) * @param border the number of border modules to add, which must be non-negative + * @param lightColor the color to use for light modules, in any format supported by CSS, not {@code null} + * @param darkColor the color to use for dark modules, in any format supported by CSS, not {@code null} * @return a string representing the QR Code as an SVG XML document - * @throws NullPointerException if the QR Code is {@code null} + * @throws NullPointerException if any object is {@code null} * @throws IllegalArgumentException if the border is negative */ - private static String toSvgString(QrCode qr, int border) { + private static String toSvgString(QrCode qr, int border, String lightColor, String darkColor) { Objects.requireNonNull(qr); + Objects.requireNonNull(lightColor); + Objects.requireNonNull(darkColor); if (border < 0) throw new IllegalArgumentException("Border must be non-negative"); long brd = border; @@ -242,7 +246,7 @@ public final class QrCodeGeneratorDemo { .append("\n") .append(String.format("\n", qr.size + brd * 2)) - .append("\t\n") + .append("\t\n") .append("\t\n") + .append("\" fill=\"" + darkColor + "\"/>\n") .append("\n") .toString(); } From 04dd0fc06c9bf977fed68c0e96100d60b54edf58 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 28 Jul 2021 19:15:14 +0000 Subject: [PATCH 82/85] Tweaked QrCodeGeneratorDemo code to use QrSegmentAdvanced.makeKanji() instead of hard-coding the data words, while maintaining identical output image. --- .../nayuki/fastqrcodegen/QrCodeGeneratorDemo.java | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java index 3f76d83..558d221 100644 --- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java +++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java @@ -134,18 +134,7 @@ public final class QrCodeGeneratorDemo { qr = QrCode.encodeText(madoka, QrCode.Ecc.LOW); writePng(toImage(qr, 9, 4, 0xFFFFE0, 0x303080), "madoka-utf8-QR.png"); - int[] kanjiChars = { // Kanji mode encoding (13 bits per character) - 0x0035, 0x1002, 0x0FC0, 0x0AED, 0x0AD7, - 0x015C, 0x0147, 0x0129, 0x0059, 0x01BD, - 0x018D, 0x018A, 0x0036, 0x0141, 0x0144, - 0x0001, 0x0000, 0x0249, 0x0240, 0x0249, - 0x0000, 0x0104, 0x0105, 0x0113, 0x0115, - 0x0000, 0x0208, 0x01FF, 0x0008, - }; - BitBuffer bb = new BitBuffer(); - for (int c : kanjiChars) - bb.appendBits(c, 13); - segs = Arrays.asList(new QrSegment(QrSegment.Mode.KANJI, kanjiChars.length, bb.data, bb.bitLength)); + segs = Arrays.asList(QrSegmentAdvanced.makeKanji(madoka)); qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); writePng(toImage(qr, 9, 4, 0xE0F0FF, 0x404040), "madoka-kanji-QR.png"); } From cb4643510cc8a5793b87d09140842f2e62ef86c9 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 31 Jul 2021 23:42:43 +0000 Subject: [PATCH 83/85] Improved the memoizer by adding fast path and slightly simplified existing logic, updated copyright year in readme document. --- Readme.markdown | 2 +- src/io/nayuki/fastqrcodegen/Memoizer.java | 29 ++++++++++++++--------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Readme.markdown b/Readme.markdown index dfcff4c..b0e0e0f 100644 --- a/Readme.markdown +++ b/Readme.markdown @@ -60,7 +60,7 @@ Examples License ------- -Copyright © 2019 Project Nayuki. (MIT License) +Copyright © 2021 Project Nayuki. (MIT License) [https://www.nayuki.io/page/fast-qr-code-generator-library](https://www.nayuki.io/page/fast-qr-code-generator-library) Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/src/io/nayuki/fastqrcodegen/Memoizer.java b/src/io/nayuki/fastqrcodegen/Memoizer.java index cbb7455..34a50b7 100644 --- a/src/io/nayuki/fastqrcodegen/Memoizer.java +++ b/src/io/nayuki/fastqrcodegen/Memoizer.java @@ -24,10 +24,10 @@ package io.nayuki.fastqrcodegen; import java.lang.ref.SoftReference; -import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; @@ -35,7 +35,7 @@ import java.util.function.Function; final class Memoizer { private final Function function; - private Map> cache = new HashMap<>(); + Map> cache = new ConcurrentHashMap<>(); private Set pending = new HashSet<>(); @@ -47,21 +47,30 @@ final class Memoizer { // Computes function.apply(arg) or returns a cached copy of a previous call. public R get(T arg) { + // Non-blocking fast path + { + SoftReference ref = cache.get(arg); + if (ref != null) { + R result = ref.get(); + if (result != null) + return result; + } + } + + // Sequential slow path while (true) { synchronized(this) { - if (cache.containsKey(arg)) { - SoftReference ref = cache.get(arg); + SoftReference ref = cache.get(arg); + if (ref != null) { R result = ref.get(); if (result != null) return result; cache.remove(arg); } - // Now cache.containsKey(arg) == false + assert !cache.containsKey(arg); - if (!pending.contains(arg)) { - pending.add(arg); + if (pending.add(arg)) break; - } try { this.wait(); @@ -73,9 +82,7 @@ final class Memoizer { try { R result = function.apply(arg); - synchronized(this) { - cache.put(arg, new SoftReference<>(result)); - } + cache.put(arg, new SoftReference<>(result)); return result; } finally { synchronized(this) { From 5615dbab5d88a59a15c7d4b69d4b837bdb3c2a41 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 5 Sep 2021 04:13:10 +0000 Subject: [PATCH 84/85] Hyphenated a phrase. --- Readme.markdown | 2 +- src/io/nayuki/fastqrcodegen/package-info.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.markdown b/Readme.markdown index b0e0e0f..399f58c 100644 --- a/Readme.markdown +++ b/Readme.markdown @@ -23,7 +23,7 @@ Core features: * Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes * Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts * Detects finder-like penalty patterns more accurately than other implementations -* Open source code under the permissive MIT License +* Open-source code under the permissive MIT License Manual parameters: diff --git a/src/io/nayuki/fastqrcodegen/package-info.java b/src/io/nayuki/fastqrcodegen/package-info.java index 0ab23a6..1c48734 100644 --- a/src/io/nayuki/fastqrcodegen/package-info.java +++ b/src/io/nayuki/fastqrcodegen/package-info.java @@ -14,7 +14,7 @@ *

    • Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes

    • *
    • Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts

    • *
    • Detects finder-like penalty patterns more accurately than other implementations

    • - *
    • Open source code under the permissive MIT License

    • + *
    • Open-source code under the permissive MIT License

    • *
    *

    Manual parameters:

    *
      From e3e82761494184a0b5848b1e2593b39f624ef0f4 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 14 Nov 2021 21:39:35 +0000 Subject: [PATCH 85/85] Renamed directory, moved file. --- Readme.markdown => java-fast/Readme.markdown | 0 {src => java-fast}/io/nayuki/fastqrcodegen/BitBuffer.java | 0 .../io/nayuki/fastqrcodegen/DataTooLongException.java | 0 {src => java-fast}/io/nayuki/fastqrcodegen/Memoizer.java | 0 {src => java-fast}/io/nayuki/fastqrcodegen/QrCode.java | 0 .../io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java | 0 {src => java-fast}/io/nayuki/fastqrcodegen/QrSegment.java | 0 {src => java-fast}/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java | 0 {src => java-fast}/io/nayuki/fastqrcodegen/QrTemplate.java | 0 .../io/nayuki/fastqrcodegen/ReedSolomonGenerator.java | 0 {src => java-fast}/io/nayuki/fastqrcodegen/package-info.java | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename Readme.markdown => java-fast/Readme.markdown (100%) rename {src => java-fast}/io/nayuki/fastqrcodegen/BitBuffer.java (100%) rename {src => java-fast}/io/nayuki/fastqrcodegen/DataTooLongException.java (100%) rename {src => java-fast}/io/nayuki/fastqrcodegen/Memoizer.java (100%) rename {src => java-fast}/io/nayuki/fastqrcodegen/QrCode.java (100%) rename {src => java-fast}/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java (100%) rename {src => java-fast}/io/nayuki/fastqrcodegen/QrSegment.java (100%) rename {src => java-fast}/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java (100%) rename {src => java-fast}/io/nayuki/fastqrcodegen/QrTemplate.java (100%) rename {src => java-fast}/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java (100%) rename {src => java-fast}/io/nayuki/fastqrcodegen/package-info.java (100%) diff --git a/Readme.markdown b/java-fast/Readme.markdown similarity index 100% rename from Readme.markdown rename to java-fast/Readme.markdown diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/java-fast/io/nayuki/fastqrcodegen/BitBuffer.java similarity index 100% rename from src/io/nayuki/fastqrcodegen/BitBuffer.java rename to java-fast/io/nayuki/fastqrcodegen/BitBuffer.java diff --git a/src/io/nayuki/fastqrcodegen/DataTooLongException.java b/java-fast/io/nayuki/fastqrcodegen/DataTooLongException.java similarity index 100% rename from src/io/nayuki/fastqrcodegen/DataTooLongException.java rename to java-fast/io/nayuki/fastqrcodegen/DataTooLongException.java diff --git a/src/io/nayuki/fastqrcodegen/Memoizer.java b/java-fast/io/nayuki/fastqrcodegen/Memoizer.java similarity index 100% rename from src/io/nayuki/fastqrcodegen/Memoizer.java rename to java-fast/io/nayuki/fastqrcodegen/Memoizer.java diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/java-fast/io/nayuki/fastqrcodegen/QrCode.java similarity index 100% rename from src/io/nayuki/fastqrcodegen/QrCode.java rename to java-fast/io/nayuki/fastqrcodegen/QrCode.java diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/java-fast/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java similarity index 100% rename from src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java rename to java-fast/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/java-fast/io/nayuki/fastqrcodegen/QrSegment.java similarity index 100% rename from src/io/nayuki/fastqrcodegen/QrSegment.java rename to java-fast/io/nayuki/fastqrcodegen/QrSegment.java diff --git a/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java b/java-fast/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java similarity index 100% rename from src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java rename to java-fast/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/java-fast/io/nayuki/fastqrcodegen/QrTemplate.java similarity index 100% rename from src/io/nayuki/fastqrcodegen/QrTemplate.java rename to java-fast/io/nayuki/fastqrcodegen/QrTemplate.java diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/java-fast/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java similarity index 100% rename from src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java rename to java-fast/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java diff --git a/src/io/nayuki/fastqrcodegen/package-info.java b/java-fast/io/nayuki/fastqrcodegen/package-info.java similarity index 100% rename from src/io/nayuki/fastqrcodegen/package-info.java rename to java-fast/io/nayuki/fastqrcodegen/package-info.java