From c1da14be6ff13763d1bcfa5b9011430c1ebb6798 Mon Sep 17 00:00:00 2001 From: Christopher Schultz Date: Sat, 17 Feb 2018 21:04:43 -0500 Subject: [PATCH 1/6] Improve performance and flexibility: - Use Writer instead of StringBuilder - Don't use unnecessary and inefficient String.format() method - Allow caller to specify whether to include XML PI, doctype, and extra whitespace. --- java/io/nayuki/qrcodegen/QrCode.java | 189 ++++++++++++++++++++++++--- 1 file changed, 173 insertions(+), 16 deletions(-) diff --git a/java/io/nayuki/qrcodegen/QrCode.java b/java/io/nayuki/qrcodegen/QrCode.java index 68d35ca..bae5d9a 100644 --- a/java/io/nayuki/qrcodegen/QrCode.java +++ b/java/io/nayuki/qrcodegen/QrCode.java @@ -24,6 +24,8 @@ package io.nayuki.qrcodegen; import java.awt.image.BufferedImage; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -91,7 +93,7 @@ public final class QrCode { * @throws IllegalArgumentException if the data is too long to fit in the largest version QR Code at the ECL */ public static QrCode encodeSegments(List segs, Ecc ecl) { - return encodeSegments(segs, ecl, MIN_VERSION, MAX_VERSION, -1, true); + return encodeSegments(segs, ecl, 1, 40, -1, true); } @@ -115,7 +117,7 @@ public final class QrCode { 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) + if (!(1 <= minVersion && minVersion <= maxVersion && maxVersion <= 40) || mask < -1 || mask > 7) throw new IllegalArgumentException("Invalid value"); // Find the minimal version number to use @@ -162,13 +164,6 @@ public final class QrCode { - /*---- Public constants ----*/ - - public static final int MIN_VERSION = 1; - public static final int MAX_VERSION = 40; - - - /*---- Instance fields ----*/ // Public immutable scalar parameters @@ -210,7 +205,7 @@ public final class QrCode { 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) + if (ver < 1 || ver > 40 || mask < -1 || mask > 7) throw new IllegalArgumentException("Value out of range"); Objects.requireNonNull(dataCodewords); @@ -276,7 +271,7 @@ public final class QrCode { * @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) { + public String toSvgString_old(int border) { if (border < 0) throw new IllegalArgumentException("Border must be non-negative"); StringBuilder sb = new StringBuilder(); @@ -303,7 +298,104 @@ public final class QrCode { sb.append("\n"); return sb.toString(); } - + + /** + * Returns a String containing a Scalable Vector Graphic (SVG) that depicts + * this QR Code symbol. + * + * @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) { + return toSvgString(border, true, true, true); + } + + /** + * Returns a String containing a Scalable Vector Graphic (SVG) that depicts + * this QR Code symbol. + * + * @param border The number of border modules to add, which must be non-negative. + * @param includeXMLPI Flag to include the XML processing instruction (<?xml ... ?>). + * @param includeDoctype Flag to include the DOCTYPE. + * @param prettyPrint Flag to include optional newlines and indents. + * + * @return A String representing this QR Code as an SVG document. + */ + public String toSvgString(int border, boolean includeXMLPI, boolean includeDoctype, boolean prettyPrint) { + StringWriter sw = new StringWriter(); + PrintWriter out = new PrintWriter(sw); + toSvg(out, border, includeXMLPI, includeDoctype, prettyPrint); + if(prettyPrint) + out.println(); // Matches previous implementation + return sw.toString(); + } + + /** + * Writes a Scalable Vector Graphic (SVG) to the specified PrintWriter + * that depicts this QR Code symbol. + * + * @param border The number of border modules to add, which must be non-negative. + * @param includeXMLPI Flag to include the XML processing instruction (<?xml ... ?>). + * @param includeDoctype Flag to include the DOCTYPE. + * @param prettyPrint Flag to include optional newlines and indents. + * + * @return A String representing this QR Code as an SVG document. + */ + public void toSvg(PrintWriter out, int border, boolean includeXMLPI, boolean includeDoctype, boolean prettyPrint) { + if (border < 0) + throw new IllegalArgumentException("Border must be non-negative"); + + if(includeXMLPI) + out.write(""); + + if(prettyPrint) + out.println(); + + if(includeDoctype) + out.write(""); + + if(prettyPrint) + out.println(); + int dim = size + (border << 1); // Double border size by shifting + out.write(""); + + if(prettyPrint) { + out.println(); + out.write('\t'); + } + out.write(""); + if(prettyPrint) { + out.println(); + out.write('\t'); + } + out.write(""); + if(prettyPrint) + out.println(); + out.write(""); + } /*---- Private helper methods for constructor: Drawing function modules ----*/ @@ -637,7 +729,7 @@ public final class QrCode { // 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) + if (ver < 1 || ver > 40) throw new IllegalArgumentException("Version number out of range"); else if (ver == 1) return new int[]{}; @@ -663,7 +755,7 @@ public final class QrCode { // 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) + if (ver < 1 || ver > 40) throw new IllegalArgumentException("Version number out of range"); int size = ver * 4 + 17; @@ -688,7 +780,7 @@ public final class QrCode { // 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) + if (ver < 1 || ver > 40) throw new IllegalArgumentException("Version number out of range"); return getNumRawDataModules(ver) / 8 - ECC_CODEWORDS_PER_BLOCK[ecl.ordinal()][ver] @@ -839,5 +931,70 @@ public final class QrCode { } } - + + static class NullWriter extends java.io.Writer { + + @Override + public void write(char[] cbuf, int off, int len) + throws java.io.IOException + { + // Do nothing + } + + @Override + public void flush() + throws java.io.IOException + { + // Do nothing + } + + @Override + public void close() + throws java.io.IOException + { + // Do nothing + } + } + + public static void main(String[] args) { + QrCode qr = QrCode.encodeText("Hello, World", Ecc.MEDIUM); + + int iterations; + if(args.length > 0) + iterations = Integer.parseInt(args[0]); + else + iterations = 99999; + + System.out.println("Running benchmark with " + iterations + " iterations..."); + System.out.println("QrCode.toSvgString_old:"); + System.out.flush(); + + long elapsed = System.currentTimeMillis(); + for(int i=0; i Date: Sat, 17 Feb 2018 21:06:02 -0500 Subject: [PATCH 2/6] Remove unnecessary NullWriter class. --- java/io/nayuki/qrcodegen/QrCode.java | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/java/io/nayuki/qrcodegen/QrCode.java b/java/io/nayuki/qrcodegen/QrCode.java index bae5d9a..c9ba9fb 100644 --- a/java/io/nayuki/qrcodegen/QrCode.java +++ b/java/io/nayuki/qrcodegen/QrCode.java @@ -932,30 +932,6 @@ public final class QrCode { } - static class NullWriter extends java.io.Writer { - - @Override - public void write(char[] cbuf, int off, int len) - throws java.io.IOException - { - // Do nothing - } - - @Override - public void flush() - throws java.io.IOException - { - // Do nothing - } - - @Override - public void close() - throws java.io.IOException - { - // Do nothing - } - } - public static void main(String[] args) { QrCode qr = QrCode.encodeText("Hello, World", Ecc.MEDIUM); From 586eb38ebe57f8d4d2adbd357ca2aff30153b6f9 Mon Sep 17 00:00:00 2001 From: Christopher Schultz Date: Sat, 17 Feb 2018 21:20:26 -0500 Subject: [PATCH 3/6] Restore constants for MIN_VERSION and MAX_VERSION. --- java/io/nayuki/qrcodegen/QrCode.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/java/io/nayuki/qrcodegen/QrCode.java b/java/io/nayuki/qrcodegen/QrCode.java index c9ba9fb..5b0bb56 100644 --- a/java/io/nayuki/qrcodegen/QrCode.java +++ b/java/io/nayuki/qrcodegen/QrCode.java @@ -93,7 +93,7 @@ public final class QrCode { * @throws IllegalArgumentException if the data is too long to fit in the largest version QR Code at the ECL */ public static QrCode encodeSegments(List segs, Ecc ecl) { - return encodeSegments(segs, ecl, 1, 40, -1, true); + return encodeSegments(segs, ecl, MIN_VERSION, MAX_VERSION, -1, true); } @@ -117,7 +117,7 @@ public final class QrCode { public static QrCode encodeSegments(List segs, Ecc ecl, int minVersion, int maxVersion, int mask, boolean boostEcl) { Objects.requireNonNull(segs); Objects.requireNonNull(ecl); - if (!(1 <= minVersion && minVersion <= maxVersion && maxVersion <= 40) || mask < -1 || mask > 7) + 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 @@ -161,10 +161,13 @@ public final class QrCode { // Create the QR Code symbol return new QrCode(version, ecl, bb.getBytes(), mask); } - - - - /*---- Instance fields ----*/ + + /*---- Public constants ----*/ + + public static final int MIN_VERSION = 1; + public static final int MAX_VERSION = 40; + + /*---- Instance fields ----*/ // Public immutable scalar parameters From 3b78df216018fb6dcc21c1c3f9544461ecde827b Mon Sep 17 00:00:00 2001 From: Christopher Schultz Date: Sat, 17 Feb 2018 21:23:24 -0500 Subject: [PATCH 4/6] Remove old toSvgString_old method. Remove main driver with performance comparison. --- java/io/nayuki/qrcodegen/QrCode.java | 80 +--------------------------- 1 file changed, 1 insertion(+), 79 deletions(-) diff --git a/java/io/nayuki/qrcodegen/QrCode.java b/java/io/nayuki/qrcodegen/QrCode.java index 5b0bb56..70978cf 100644 --- a/java/io/nayuki/qrcodegen/QrCode.java +++ b/java/io/nayuki/qrcodegen/QrCode.java @@ -265,44 +265,8 @@ public final class QrCode { } 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_old(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(); - } - /** + /** * Returns a String containing a Scalable Vector Graphic (SVG) that depicts * this QR Code symbol. * @@ -934,46 +898,4 @@ public final class QrCode { } } - - public static void main(String[] args) { - QrCode qr = QrCode.encodeText("Hello, World", Ecc.MEDIUM); - - int iterations; - if(args.length > 0) - iterations = Integer.parseInt(args[0]); - else - iterations = 99999; - - System.out.println("Running benchmark with " + iterations + " iterations..."); - System.out.println("QrCode.toSvgString_old:"); - System.out.flush(); - - long elapsed = System.currentTimeMillis(); - for(int i=0; i Date: Sat, 17 Feb 2018 21:41:05 -0500 Subject: [PATCH 5/6] Remove unnecessary SVG components: - Stroke and fill - element --- java/io/nayuki/qrcodegen/QrCode.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/java/io/nayuki/qrcodegen/QrCode.java b/java/io/nayuki/qrcodegen/QrCode.java index 70978cf..71f4e45 100644 --- a/java/io/nayuki/qrcodegen/QrCode.java +++ b/java/io/nayuki/qrcodegen/QrCode.java @@ -329,17 +329,13 @@ public final class QrCode { out.print(dim); out.write(' '); out.print(dim); - out.write("\" stroke=\"none\">"); + out.write("\">", 0, 2); if(prettyPrint) { out.println(); out.write('\t'); } - out.write(""); - if(prettyPrint) { - out.println(); - out.write('\t'); - } + out.write(""); + out.write("\"/>"); if(prettyPrint) out.println(); out.write(""); From 0dc316c2f72a9ab0d825661c375cd05b63bd542a Mon Sep 17 00:00:00 2001 From: Christopher Schultz Date: Thu, 22 Feb 2018 07:41:36 -0500 Subject: [PATCH 6/6] Restore original whitespace. Restore additional MIN_VERSION/MAX_VERSION pair. --- java/io/nayuki/qrcodegen/QrCode.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/java/io/nayuki/qrcodegen/QrCode.java b/java/io/nayuki/qrcodegen/QrCode.java index 71f4e45..ec76da3 100644 --- a/java/io/nayuki/qrcodegen/QrCode.java +++ b/java/io/nayuki/qrcodegen/QrCode.java @@ -162,12 +162,12 @@ public final class QrCode { return new QrCode(version, ecl, bb.getBytes(), mask); } - /*---- Public constants ----*/ + /*---- Public constants ----*/ - public static final int MIN_VERSION = 1; - public static final int MAX_VERSION = 40; + public static final int MIN_VERSION = 1; + public static final int MAX_VERSION = 40; - /*---- Instance fields ----*/ + /*---- Instance fields ----*/ // Public immutable scalar parameters @@ -208,7 +208,7 @@ public final class QrCode { public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) { // Check arguments Objects.requireNonNull(ecl); - if (ver < 1 || ver > 40 || mask < -1 || mask > 7) + if (ver < MIN_VERSION || ver > MAX_VERSION || mask < -1 || mask > 7) throw new IllegalArgumentException("Value out of range"); Objects.requireNonNull(dataCodewords);