生成中间带logo的二维码

时间:2022-11-17 20:41:36

1.首先在maven中导入zxing的工具包

<!-- https://mvnrepository.com/artifact/com.google.zxing/core -->
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.3.0</version>
</dependency>
2.具体工具类

package com.business.util;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Map;

import javax.imageio.ImageIO;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Charsets;
import com.google.common.collect.Maps;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Binarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.NotFoundException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

/**
 * 二维码工具类
*
* @author zhanghao   2017年10月1日
*
 */
public class QrcodeUtils {
    
  private static final int DEFAULT_LENGTH = 400;// 生成二维码的默认边长,因为是正方形的,所以高度和宽度一致
  
  private static final String FORMAT = "png";// 生成二维码的格式
  
  public static final int BLACK = 0xFF000000;
  public static final int WHITE = 0xFFFFFFFF;

  private static Logger logger = LoggerFactory.getLogger(QrcodeUtils.class);

  /**
   * 根据指定边长创建生成的二维码
   *
   * @param content  二维码内容
   * @param length   二维码的高度和宽度
   * @param logoFile logo 文件对象,可以为空
   * @return 二维码图片的字节数组
   */
  public static byte[] createQrcode(String content, int length, File logoFile) {
    //logoFile为二维码中间的logo图片
    if (logoFile != null && !logoFile.exists()) {
      throw new IllegalArgumentException("请提供正确的logo文件!");
    }
    Map<EncodeHintType, Object> hints = Maps.newEnumMap(EncodeHintType.class);
    // 设置字符编码
    hints.put(EncodeHintType.CHARACTER_SET, Charsets.UTF_8.name());
    // 指定纠错等级
    hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
    
    hints.put(EncodeHintType.MARGIN, 1);
    try {
        BitMatrix bitMatrix = new MultiFormatWriter().encode(content,BarcodeFormat.QR_CODE, length, length, hints);
        int width = bitMatrix.getWidth();
        int height = bitMatrix.getHeight();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        for (int y = 0; y < height; y++) {
          for (int x = 0; x < width; x++) {
              image.setRGB(x, y, bitMatrix.get(x, y) ? BLACK : WHITE);
          }
        }
        if (logoFile != null) {
           // 添加logo图片到二维码, 此处一定需要重新进行读取,而不能直接使用二维码的BufferedImage 对象
           overlapImage(image,logoFile, new MatrixToLogoImageConfig());
        }
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, FORMAT, outputStream);
        
        return outputStream.toByteArray();
    } catch (Exception e) {
      logger.warn("内容为:【" + content + "】的二维码生成失败!", e);
      return null;
    }
  }

    /**
     * 创建生成默认高度(400)的二维码图片 可以指定是否加logo
     *
     * @param content 二维码内容
     * @param logoFile logo 文件对象,可以为空
     * @return 二维码图片的字节数组
     */
    public static byte[] createQrcode(String content,File logoFile) {
        return createQrcode(content, DEFAULT_LENGTH, logoFile);
    }
  
    /**
     * 将logo添加到二维码中间
     *
     * @param image 生成的二维码图片对象
     * @param logoFile logo文件对象
     */
    private static void overlapImage(BufferedImage image, File logoFile,MatrixToLogoImageConfig logoConfig) throws IOException {
        try {
            BufferedImage logo = ImageIO.read(logoFile);
            // 考虑到logo图片贴到二维码中,建议大小不要超过二维码的1/5;
            int width = image.getWidth() / logoConfig.getLogoPart();
            int height = image.getHeight() / logoConfig.getLogoPart();
            // logo起始位置,此目的是为logo居中显示
            int x = (image.getWidth() - width) / 2;
            int y = (image.getHeight() - height) / 2;
            
            Graphics2D g = image.createGraphics();
            // 绘制图
            g.drawImage(logo, x, y, width, height, null);

            // 给logo画边框
            // 构造一个具有指定线条宽度以及 cap 和 join 风格的默认值的实心 BasicStroke
            g.setStroke(new BasicStroke(logoConfig.getBorder()));
            g.setColor(logoConfig.getBorderColor());
            g.drawRect(x, y, width, height);

            g.dispose();
        } catch (Exception e) {
            throw new IOException("二维码添加logo时发生异常!", e);
        }
    }

  /**
   * 解析二维码
   *
   * @param file 二维码文件内容
   * @return 二维码的内容
   */
  public static String decodeQrcode(File file) throws IOException, NotFoundException {
    BufferedImage image = ImageIO.read(file);
    LuminanceSource source = new BufferedImageLuminanceSource(image);
    Binarizer binarizer = new HybridBinarizer(source);
    BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
    Map<DecodeHintType, Object> hints = Maps.newEnumMap(DecodeHintType.class);
    hints.put(DecodeHintType.CHARACTER_SET, Charsets.UTF_8.name());
    hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
    hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
    return new MultiFormatReader().decode(binaryBitmap, hints).getText();
  }
  
  protected static class MatrixToLogoImageConfig {
        // logo默认边框颜色
        public static final Color DEFAULT_BORDERCOLOR = Color.WHITE;
        // logo默认边框宽度
        public static final int DEFAULT_BORDER = 2;
        // logo大小默认为照片的1/5
        public static final int DEFAULT_LOGOPART = 5;

        private final int border = DEFAULT_BORDER;
        private final Color borderColor;
        private final int logoPart;

        public MatrixToLogoImageConfig() {
            this(DEFAULT_BORDERCOLOR, DEFAULT_LOGOPART);
        }

        public MatrixToLogoImageConfig(Color borderColor, int logoPart) {
            this.borderColor = borderColor;
            this.logoPart = logoPart;
        }

        public Color getBorderColor() {
            return this.borderColor;
        }

        public int getBorder() {
            return this.border;
        }

        public int getLogoPart() {
            return this.logoPart;
        }
    }
  
  protected static final class BufferedImageLuminanceSource extends LuminanceSource {

      private final BufferedImage image;
      private final int left;
      private final int top;

      public BufferedImageLuminanceSource(BufferedImage image) {
        this(image, 0, 0, image.getWidth(), image.getHeight());
      }

      public BufferedImageLuminanceSource(BufferedImage image, int left, int top,
                                          int width, int height) {
        super(width, height);

        int sourceWidth = image.getWidth();
        int sourceHeight = image.getHeight();
        if (left + width > sourceWidth || top + height > sourceHeight) {
          throw new IllegalArgumentException(
            "Crop rectangle does not fit within image data.");
        }

        for (int y = top; y < top + height; y++) {
          for (int x = left; x < left + width; x++) {
            if ((image.getRGB(x, y) & 0xFF000000) == 0) {
              image.setRGB(x, y, 0xFFFFFFFF);// = white
            }
          }
        }

        this.image = new BufferedImage(sourceWidth, sourceHeight,
          BufferedImage.TYPE_BYTE_GRAY);
        this.image.getGraphics().drawImage(image, 0, 0, null);
        this.left = left;
        this.top = top;
      }

      @Override
      public byte[] getRow(int y, byte[] row) {
        if (y < 0 || y >= getHeight()) {
          throw new IllegalArgumentException(
            "Requested row is outside the image: " + y);
        }
        int width = getWidth();
        if (row == null || row.length < width) {
          row = new byte[width];
        }
        this.image.getRaster().getDataElements(this.left, this.top + y, width,
          1, row);
        return row;
      }

      @Override
      public byte[] getMatrix() {
        int width = getWidth();
        int height = getHeight();
        int area = width * height;
        byte[] matrix = new byte[area];
        this.image.getRaster().getDataElements(this.left, this.top, width,
          height, matrix);
        return matrix;
      }

      @Override
      public boolean isCropSupported() {
        return true;
      }

      @Override
      public LuminanceSource crop(int left, int top, int width, int height) {
        return new BufferedImageLuminanceSource(this.image, this.left + left,
          this.top + top, width, height);
      }

      @Override
      public boolean isRotateSupported() {
        return true;
      }

      @Override
      public LuminanceSource rotateCounterClockwise() {

        int sourceWidth = this.image.getWidth();
        int sourceHeight = this.image.getHeight();

        AffineTransform transform = new AffineTransform(0.0, -1.0, 1.0, 0.0,
          0.0, sourceWidth);

        BufferedImage rotatedImage = new BufferedImage(sourceHeight,
          sourceWidth, BufferedImage.TYPE_BYTE_GRAY);

        Graphics2D g = rotatedImage.createGraphics();
        g.drawImage(this.image, transform, null);
        g.dispose();

        int width = getWidth();
        return new BufferedImageLuminanceSource(rotatedImage, this.top,
          sourceWidth - (this.left + width), getHeight(), width);
      }
  }
  
  public static void main(String[] args) throws IOException, NotFoundException {
        String outpath = "F:/qrcode.png";
        File logoFile = new File("F:\\timg.jpg");
        byte[] bytes = QrcodeUtils.createQrcode("https://www.baidu.com", logoFile);
        FileUtils.writeByteArrayToFile(new File(outpath), bytes);
        //System.out.println(QrcodeUtils.decodeQrcode(new File(outpath)));
    }
}