From d410c550f8cd732805a9609add9b426aa7101b6b Mon Sep 17 00:00:00 2001 From: Charlie Hubbard Date: Tue, 27 Aug 2024 19:01:07 -0400 Subject: [PATCH] Create QrCodeRenderer.java Added a QRCodeRenderer to make rendering QRCodes reusable out of the box. This creates a much more usable experience without needing to read and understand lower level details about graphics, QRCode, etc. --- .../io/nayuki/qrcodegen/QrCodeRenderer.java | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 java/src/main/java/io/nayuki/qrcodegen/QrCodeRenderer.java diff --git a/java/src/main/java/io/nayuki/qrcodegen/QrCodeRenderer.java b/java/src/main/java/io/nayuki/qrcodegen/QrCodeRenderer.java new file mode 100644 index 0000000..d6f6dc7 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/QrCodeRenderer.java @@ -0,0 +1,208 @@ +package com.fuseanalytics.archiver.util; + +import io.nayuki.qrcodegen.QrCode; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Use this class to generate images of your {@link io.nayuki.qrcodegen.QrCode} instances. + * It uses sensible defaults to make the resulting QRCode look presentable without much + * tweaking. This class can be used by multiple threads provided after construction and + * configuration. So any render* method will not cause threading issues, but calling the + * configuration methods {@link #colors(Color, Color)}, {@link #border(int)}, {@link #scale} + * will cause unpredictable errors. As long as you use the configuration methods on one + * thread before sharing the instance and only use render* methods from multiple threads + * this class is thread safe. + *

+ * Here is a simple example of generating a {@see java.awt.BufferedImage}: + *

+ * + *
{@code
+ *  QrCode qr = QrCode.encodeText( "Hello World!", QrCode.Ecc.MEDIUM );
+ *  BufferedImage img = new QrCodeRenderer().renderImage(qrCode);
+ * }
+ * + *

+ * Here is an example, for rendering a PNG to a file: + *

+ *
{@code
+ *  QrCode qr = QrCode.encodeText( "Hello World!", QrCode.Ecc.MEDIUM );
+ *  File target = new QrCodeRenderer().renderFile(qrCode, "png", new File("output.png"));
+ * }
+ * + *

+ * Here's an example of tweaking the defaults and rendering a PNG to an OutputStream: + *

+ *
{@code
+ *  QrCode qr = QrCode.encodeText( "Hello World!", QrCode.Ecc.MEDIUM );
+ *  ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ *  File target = new QrCodeRenderer()
+ *      .scale(4)
+ *      .border(10)
+ *      .colors(Color.BLUE, Color.WHITE)
+ *      .renderStream(qrCode, "png", baos);
+ * }
+ * + *

+ * Here's an example, for rendering to SVG: + *

+ *
{@code
+ *  QrCode qr = QrCode.encodeText( "Hello World!", QrCode.Ecc.MEDIUM );
+ *  String svg = new QrCodeRenderer().renderSvg(qrCode);
+ * }
+ */ +public class QrCodeRenderer { + + /** + * The scale to use when rendering bitmap based images. Default is 5. + */ + private int scale = 5; + + /** + * The size of border in pixels. THe border will be scaled by the scale factor. + * This provides some whitespace around QR code. This area will be rendered using + * the light color. Default is 2. + */ + private int border = 2; + + /** + * This the dark color used to render the QR code. The default is black. + */ + private Color darkColor = Color.BLACK; + + /** + * This is the light color used to render the background of the QR Code. + */ + private Color lightColor = Color.WHITE; + + /** + * Renders a BufferedImage of the given QRCode object. + * @param qrCode the QR code to use to render into a BufferedImage. + * @return The bitmap rendering of the given QR Code. + */ + public BufferedImage renderImage(QrCode qrCode) { + BufferedImage img = new BufferedImage((qrCode.size + border * 2) * scale, (qrCode.size + border * 2) * scale, BufferedImage.TYPE_INT_RGB); + for( int y = 0; y < img.getHeight(); y++ ) { + for( int x = 0; x < img.getWidth(); x++ ) { + int sx = x / scale - border; + int sy = y / scale - border; + boolean color = qrCode.getModule(sx,sy); + img.setRGB(x, y, color ? darkColor.getRGB() : lightColor.getRGB() ); + } + } + return img; + } + + /** + * Renders an SVG document of the given QR Code. + * @param qrCode the QRCode to render into the SVG document. + * @return a String that contains the SVG markup of the QR Code. + */ + public String renderSvg(QrCode qrCode) { + long brd = border; + + StringBuilder buffer = new StringBuilder() + .append("\n") + .append("\n") + .append(String.format("\n", + qrCode.size + brd * 2)) + .append("\t\n") + .append("\t\n") + .append("\n") + .toString(); + } + + /** + * Renders the QR Code to a bitmap and writes that to the given file in the given format provided. + * @param qrCode The QRCode to generate the bitmap of. + * @param formatName The image format to write (png, jpeg, etc). These are the same formats supported by {@see javax.imageio.ImageIO}. + * @param output A java.io.File you want to write the given QRCode in the given format into. + * @return Returns the given File for convenience that contains the bitmap of the QRCode. + * @throws IOException if any IOException happens while writing to the given File. + * @throws IllegalArgumentException if the given formatName cannot be found in ImageIO library. + */ + public File renderFile(QrCode qrCode, String formatName, File output) throws IOException { + BufferedImage img = renderImage(qrCode); + if( ImageIO.write( img, formatName, output ) ) { + return output; + } else { + throw new IllegalArgumentException("No appropriate writer found for formatName=" + formatName ); + } + } + + /** + * Renders the QR Code to a bitmap and writes that to the given OutputStream in the given format provided. + * @param qrCode The QRCode to generate the bitmap of. + * @param formatName The image format to write (png, jpeg, etc). These are the same formats supported by {@see javax.imageio.ImageIO}. + * @param stream The OutputStream in which to write the bitmap image of the given QRCode in the given formatName into. + * @throws IOException if any IOException happens while writing to the given OutputStream. + * @throws IllegalArgumentException if the given formatName cannot be found in ImageIO library. + */ + public void renderStream(QrCode qrCode, String formatName, OutputStream stream ) throws IOException { + BufferedImage img = renderImage( qrCode ); + if( !ImageIO.write( img, formatName, stream ) ) { + throw new IllegalArgumentException("No appropriate writer found for formatName=" + formatName ); + } + } + + private String toRgb(Color color) { + return "rgb(" + color.getRed() + " " + color.getGreen() + " " + color.getBlue() + ")"; + } + + /** + * Overwrite the border default by specifying the number of pixels to use as a border. This is + * the number of pixels to use before scaling is applied so the resulting border will be scaled + * up if scale > 1. + * @param border pixels to add around the resulting QRCode bitmap + * @return this + */ + public QrCodeRenderer border( int border ) { + if( border < 0 ) throw new IllegalArgumentException("Border must be non-negative: " + border ); + if( border > Integer.MAX_VALUE / 2 ) throw new IllegalArgumentException("Border size is too large."); + this.border = border; + return this; + } + + /** + * Sets the scale to use when writing into the Bitmap image. + * + * @param scale the amount to scale the image by. 1 performs no sale, 2 doubles it, 3 triples, etc. + * @return this + */ + public QrCodeRenderer scale( int scale ) { + if( scale < 0 ) throw new IllegalArgumentException("Scale must be non-negative: " + scale ); + if( (QrCode.MAX_VERSION * 4 + 17) * scale + border*2 > Integer.MAX_VALUE / scale ) throw new IllegalArgumentException("Scale is too large: " + scale); + this.scale = scale; + return this; + } + + /** + * Sets the colors to use when rendering the QRCode. The dark color will be used to write + * the QR Code, and the light color will be used as the background. + * @param darkColor the color to use for the QRCode + * @param lightColor the color to use as the background color. + * @return this + */ + public QrCodeRenderer colors( Color darkColor, Color lightColor ) { + this.darkColor = darkColor; + this.lightColor = lightColor; + return this; + } +}