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.
pull/205/head
Charlie Hubbard 1 year ago committed by GitHub
parent 42a886d784
commit d410c550f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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.
* <p>
* Here is a simple example of generating a {@see java.awt.BufferedImage}:
* </p>
*
* <pre>{@code
* QrCode qr = QrCode.encodeText( "Hello World!", QrCode.Ecc.MEDIUM );
* BufferedImage img = new QrCodeRenderer().renderImage(qrCode);
* }</pre>
*
* <p>
* Here is an example, for rendering a PNG to a file:
* </p>
* <pre>{@code
* QrCode qr = QrCode.encodeText( "Hello World!", QrCode.Ecc.MEDIUM );
* File target = new QrCodeRenderer().renderFile(qrCode, "png", new File("output.png"));
* }</pre>
*
* <p>
* Here's an example of tweaking the defaults and rendering a PNG to an OutputStream:
* </p>
* <pre>{@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);
* }</pre>
*
* <p>
* Here's an example, for rendering to SVG:
* </p>
* <pre>{@code
* QrCode qr = QrCode.encodeText( "Hello World!", QrCode.Ecc.MEDIUM );
* String svg = new QrCodeRenderer().renderSvg(qrCode);
* }</pre>
*/
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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
.append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n")
.append(String.format("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 %1$d %1$d\" stroke=\"none\">\n",
qrCode.size + brd * 2))
.append("\t<rect width=\"100%\" height=\"100%\" fill=\"").append(toRgb(lightColor)).append("\"/>\n")
.append("\t<path d=\"");
for (int y = 0; y < qrCode.size; y++) {
for (int x = 0; x < qrCode.size; x++) {
if (qrCode.getModule(x, y)) {
if (x != 0 || y != 0)
buffer.append(" ");
buffer.append(String.format("M%d,%dh1v1h-1z", x + brd, y + brd));
}
}
}
return buffer
.append("\" fill=\"").append(toRgb(darkColor)).append("\"/>\n")
.append("</svg>\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;
}
}
Loading…
Cancel
Save