Generate custom QRCode with logo image using zxing
Recently I was working on a project and we needed a service that create QrCodes with user profile image on top of it, something like the twitter QRCode. We had 2 option: Using a paid existing web service, or make our own.I went with the second option.
After a quick google search, I found zxing. At heart is it a pure Java library for decoding barcodes (the core/ and javase/ modules). It also contains several applications for Android, Google Glass, a JavaEE web application, and a GWT-based encoder application.
After 2 days playing with it, I was able to create a small Rest service, using Spring boot, that generate QrCodes for our user! Crazy.
Below a small java class that generate the same thing. It generate a QRCode 300x300 png image, that contains some content, and overly an image on top of it. The class also contains an Enumeration of 6 colors to play with Qrcode foreground and background colors (I use orange and white on this example). The class contains also a method to generate random title for generated QrCodes images.
public class QrCode {
private final String DIR = "/directory/to/save/images";
private final String ext = ".png";
private final String LOGO = "logo_url";
private final String CONTENT = "some content here";
private final int WIDTH = 300;
private final int HEIGHT = 300;
public void generate() {
// Create new configuration that specifies the error correction
Map<EncodeHintType, ErrorCorrectionLevel> hints = new HashMap<>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
QRCodeWriter writer = new QRCodeWriter();
BitMatrix bitMatrix = null;
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
// init directory
cleanDirectory(DIR);
initDirectory(DIR);
// Create a qr code with the url as content and a size of WxH px
bitMatrix = writer.encode(CONTENT, BarcodeFormat.QR_CODE, WIDTH, HEIGHT, hints);
// Load QR image
BufferedImage qrImage = MatrixToImageWriter.toBufferedImage(bitMatrix, getMatrixConfig());
// Load logo image
BufferedImage overly = getOverly(LOGO);
// Calculate the delta height and width between QR code and logo
int deltaHeight = qrImage.getHeight() - overly.getHeight();
int deltaWidth = qrImage.getWidth() - overly.getWidth();
// Initialize combined image
BufferedImage combined = new BufferedImage(qrImage.getHeight(), qrImage.getWidth(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) combined.getGraphics();
// Write QR code to new image at position 0/0
g.drawImage(qrImage, 0, 0, null);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
// Write logo into combine image at position (deltaWidth / 2) and
// (deltaHeight / 2). Background: Left/Right and Top/Bottom must be
// the same space for the logo to be centered
g.drawImage(overly, (int) Math.round(deltaWidth / 2), (int) Math.round(deltaHeight / 2), null);
// Write combined image as PNG to OutputStream
ImageIO.write(combined, "png", os);
// Store Image
Files.copy( new ByteArrayInputStream(os.toByteArray()), Paths.get(DIR + generateRandoTitle(new Random(), 9) +ext), StandardCopyOption.REPLACE_EXISTING);
} catch (WriterException e) {
e.printStackTrace();
//LOG.error("WriterException occured", e);
} catch (IOException e) {
e.printStackTrace();
//LOG.error("IOException occured", e);
}
}
private BufferedImage getOverly(String LOGO) throws IOException {
URL url = new URL(LOGO);
return ImageIO.read(url);
}
private void initDirectory(String DIR) throws IOException {
Files.createDirectories(Paths.get(DIR));
}
private void cleanDirectory(String DIR) {
try {
Files.walk(Paths.get(DIR), FileVisitOption.FOLLOW_LINKS)
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
} catch (IOException e) {
// Directory does not exist, Do nothing
}
}
private MatrixToImageConfig getMatrixConfig() {
// ARGB Colors
// Check Colors ENUM
return new MatrixToImageConfig(QrCode.Colors.WHITE.getArgb(), QrCode.Colors.ORANGE.getArgb());
}
private String generateRandoTitle(Random random, int length) {
return random.ints(48, 122)
.filter(i -> (i < 57 || i > 65) && (i < 90 || i > 97))
.mapToObj(i -> (char) i)
.limit(length)
.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
.toString();
}
public enum Colors {
BLUE(0xFF40BAD0),
RED(0xFFE91C43),
PURPLE(0xFF8A4F9E),
ORANGE(0xFFF4B13D),
WHITE(0xFFFFFFFF),
BLACK(0xFF000000);
private final int argb;
Colors(final int argb){
this.argb = argb;
}
public int getArgb(){
return argb;
}
}
}
If you execute this script, you'll get something like:
The full version of the code is available on github ;)