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 ;)