本封装采用建造者模式来开发,支持生成base64,文件。logo文件支持本地和线上。最后的调用是这样的。可以使用的前提是要依赖zxing。
String string = new QrCodeGenWrapper.Builder() .msg("123456") .logo("http://shop.szschina.cn/images/logo.png") .build() .asString(); System.out.println("=======string======="+string);
上图是表示生成了一个base64的图片
贴代码(生成类):
public class QrCodeGenWrapper { /** * 生成二维里面的内容 */ private String msg; /** * 二维码中间的logo */ private String logo; /** * logo的样式, 目前支持圆角+普通 */ private LogoStyle logoStyle; /** * 生成二维码的宽 */ private Integer width; /** * 生成二维码的高 */ private Integer height; /** * 生成二维码的颜色 */ private MatrixToImageConfig matrixToImageConfig; /** * 识别度 */ private Map<EncodeHintType, Object> hints; /** * 0 - 4 */ private Integer padding; /** * 生成二维码图片的格式 png, jpg */ private String picType; public QrCodeGenWrapper() { this(new Builder()); } QrCodeGenWrapper(Builder builder) { this.logo = builder.logo; this.width = builder.width; this.height = builder.height; this.logoStyle = builder.logoStyle; this.hints = builder.hints; this.matrixToImageConfig = builder.matrixToImageConfig; this.picType = builder.picType; if (Strings.isNullOrEmpty(builder.msg)) { throw new NullPointerException("msg is not null"); } this.msg = builder.msg; } public String msg() { return msg; } public String logo() { return logo; } public LogoStyle logoStyle() { return logoStyle; } public Integer width() { return width; } public Integer height() { return height; } public Integer padding() { return padding; } public MatrixToImageConfig matrixToImageConfig() { return matrixToImageConfig; } public Map<EncodeHintType, Object> hints() { return hints; } public String picType() { return picType; } public Builder newBuilder() { return new Builder(this); } private BufferedImage asBufferedImage() { BitMatrix bitMatrix = QrCodeUtil.encode(this); return QrCodeUtil.toBufferedImage(this, bitMatrix); } public String asString() { BufferedImage bufferedImage = asBufferedImage(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { ImageIO.write(bufferedImage, this.picType(), outputStream); } catch (IOException e) { e.printStackTrace(); } return Base64Util.encode(outputStream); } public boolean asFile(String absFileName) { File file = new File(absFileName); BufferedImage bufferedImage = asBufferedImage(); try { if (!ImageIO.write(bufferedImage, this.picType(), file)) { throw new IOException("save qrcode image error!"); } } catch (IOException e) { e.printStackTrace(); } return true; } public static class Builder { private static final MatrixToImageConfig DEFAULT_CONFIG = new MatrixToImageConfig(); /** * qrcode message's code, default UTF-8 */ private String code = "utf-8"; private String msg; private String logo; private LogoStyle logoStyle = LogoStyle.ROUND; private Integer width; private Integer height; private MatrixToImageConfig matrixToImageConfig; private Map<EncodeHintType, Object> hints; private Integer padding; private String picType = "png"; public Builder() { this.msg = ""; this.logoStyle = LogoStyle.ROUND; this.width = 200; this.height = 200; this.logo = null; this.matrixToImageConfig = DEFAULT_CONFIG; this.padding = 1; Map<EncodeHintType, Object> hints = new HashMap<>(3); hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); hints.put(EncodeHintType.CHARACTER_SET, code); hints.put(EncodeHintType.MARGIN, this.padding); this.hints = hints; } Builder(QrCodeGenWrapper wrapper) { this.logo = wrapper.logo; this.width = wrapper.width; this.height = wrapper.height; this.logoStyle = wrapper.logoStyle; this.hints = wrapper.hints; this.matrixToImageConfig = wrapper.matrixToImageConfig; this.picType = wrapper.picType; if (Strings.isNullOrEmpty(wrapper.msg)) { throw new NullPointerException("msg is not null"); } this.msg = wrapper.msg; } public Builder msg(String msg) { this.msg = msg; return this; } public Builder logo(String logo) { this.logo = logo; return this; } public Builder logoStyle(LogoStyle logoStyle) { this.logoStyle = logoStyle; return this; } public Builder width(Integer width) { this.width = width; return this; } public Builder height(Integer height) { this.height = height; return this; } public Builder matrixToImageConfig(MatrixToImageConfig matrixToImageConfig) { this.matrixToImageConfig = matrixToImageConfig; return this; } public Builder hints(Map<EncodeHintType, Object> hints) { this.hints = hints; return this; } public Builder padding(Integer padding) { this.padding = padding; return this; } public Builder picType(String picType) { this.picType = picType; return this; } public QrCodeGenWrapper build() { return new QrCodeGenWrapper(this); } } }
生成二维码的工具类:
@Slf4j public class QrCodeUtil { private static final int QUIET_ZONE_SIZE = 4; public static BitMatrix encode(QrCodeGenWrapper wrapper) { if (Strings.isNullOrEmpty(wrapper.msg())) { throw new NullPointerException("msg Is Not Null"); } ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L; int quietZone = 1; if (wrapper.hints() != null) { if (wrapper.hints().containsKey(EncodeHintType.ERROR_CORRECTION)) { errorCorrectionLevel = ErrorCorrectionLevel.valueOf(wrapper.hints().get(EncodeHintType.ERROR_CORRECTION).toString()); } if (wrapper.hints().containsKey(EncodeHintType.MARGIN)) { quietZone = Integer.parseInt(wrapper.hints().get(EncodeHintType.MARGIN).toString()); } if (quietZone > QUIET_ZONE_SIZE) { quietZone = QUIET_ZONE_SIZE; } else if (quietZone < 0) { quietZone = 0; } } try { QRCode code = Encoder.encode(wrapper.msg(), errorCorrectionLevel, wrapper.hints()); return renderResult(code, wrapper.width(), wrapper.height(), quietZone); } catch (WriterException e) { e.printStackTrace(); return null; } } private static BitMatrix renderResult(QRCode code, int width, int height, int quietZone) { ByteMatrix input = code.getMatrix(); if (input == null) { throw new IllegalStateException(); } // xxx 二维码宽高相等, 即 qrWidth == qrHeight int inputWidth = input.getWidth(); int inputHeight = input.getHeight(); int qrWidth = inputWidth + (quietZone * 2); int qrHeight = inputHeight + (quietZone * 2); // 白边过多时, 缩放 int minSize = Math.min(width, height); int scale = calculateScale(qrWidth, minSize); if (scale > 0) { if (log.isDebugEnabled()) { log.debug("qrCode scale enable! scale: {}, qrSize:{}, expectSize:{}x{}", scale, qrWidth, width, height); } int padding, tmpValue; // 计算边框留白 padding = (minSize - qrWidth * scale) / QUIET_ZONE_SIZE * quietZone; tmpValue = qrWidth * scale + padding; if (width == height) { width = tmpValue; height = tmpValue; } else if (width > height) { width = width * tmpValue / height; height = tmpValue; } else { height = height * tmpValue / width; width = tmpValue; } } int outputWidth = Math.max(width, qrWidth); int outputHeight = Math.max(height, qrHeight); int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight); int leftPadding = (outputWidth - (inputWidth * multiple)) / 2; int topPadding = (outputHeight - (inputHeight * multiple)) / 2; BitMatrix output = new BitMatrix(outputWidth, outputHeight); for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) { // Write the contents of this row of the barcode for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) { if (input.get(inputX, inputY) == 1) { output.setRegion(outputX, outputY, multiple, multiple); } } } return output; } /** * 如果留白超过15% , 则需要缩放 * (15% 可以根据实际需要进行修改) * * @param qrCodeSize 二维码大小 * @param expectSize 期望输出大小 * @return 返回缩放比例, <= 0 则表示不缩放, 否则指定缩放参数 */ private static int calculateScale(int qrCodeSize, int expectSize) { if (qrCodeSize >= expectSize) { return 0; } int scale = expectSize / qrCodeSize; int abs = expectSize - scale * qrCodeSize; if (abs < expectSize * 0.15) { return 0; } return scale; } /** * 根据二维码配置 & 二维码矩阵生成二维码图片 * * @param bitMatrix * @return * @throws IOException */ public static BufferedImage toBufferedImage(QrCodeGenWrapper wrapper, BitMatrix bitMatrix) { int qrCodeWidth = bitMatrix.getWidth(); int qrCodeHeight = bitMatrix.getHeight(); BufferedImage qrCode = new BufferedImage(qrCodeWidth, qrCodeHeight, BufferedImage.TYPE_INT_RGB); for (int x = 0; x < qrCodeWidth; x++) { for (int y = 0; y < qrCodeHeight; y++) { qrCode.setRGB(x, y, bitMatrix.get(x, y) ? wrapper.matrixToImageConfig().getPixelOnColor() : wrapper.matrixToImageConfig().getPixelOffColor()); } } // 插入logo if (!(Strings.isNullOrEmpty(wrapper.logo()))) { try { ImageUtil.insertLogo(qrCode, wrapper.logo(), wrapper.logoStyle()); } catch (IOException e) { e.printStackTrace(); } } // 若二维码的实际宽高和预期的宽高不一致, 则缩放 int realQrCodeWidth = wrapper.width(); int realQrCodeHeight = wrapper.height(); if (qrCodeWidth != realQrCodeWidth || qrCodeHeight != realQrCodeHeight) { BufferedImage tmp = new BufferedImage(realQrCodeWidth, realQrCodeHeight, BufferedImage.TYPE_INT_RGB); tmp.getGraphics().drawImage( qrCode.getScaledInstance(realQrCodeWidth, realQrCodeHeight, Image.SCALE_SMOOTH), 0, 0, null); qrCode = tmp; } return qrCode; } }
base64转换工具类:
public class Base64Util { public static String encode(ByteArrayOutputStream outputStream) { return Base64.getEncoder().encodeToString(outputStream.toByteArray()); } }
logo处理工具类:
public class ImageUtil { /** * 在图片中间,插入圆角的logo * * @param qrCode 原图 * @param logo logo地址 * @throws IOException */ public static void insertLogo(BufferedImage qrCode, String logo, LogoStyle logoStyle) throws IOException { int QRCODE_WIDTH = qrCode.getWidth(); int QRCODE_HEIGHT = qrCode.getHeight(); // 获取logo图片 BufferedImage bf = getImageByPath(logo); int size = bf.getWidth() > QRCODE_WIDTH * 2 / 10 ? QRCODE_WIDTH * 2 / 50 : bf.getWidth() / 5; bf = ImageUtil.makeRoundBorder(bf, logoStyle, size, Color.WHITE); // 边距为二维码图片的1/10 // logo的宽高 int w = bf.getWidth() > QRCODE_WIDTH * 2 / 10 ? QRCODE_WIDTH * 2 / 10 : bf.getWidth(); int h = bf.getHeight() > QRCODE_HEIGHT * 2 / 10 ? QRCODE_HEIGHT * 2 / 10 : bf.getHeight(); // 插入LOGO Graphics2D graph = qrCode.createGraphics(); int x = (QRCODE_WIDTH - w) / 2; int y = (QRCODE_HEIGHT - h) / 2; graph.drawImage(bf, x, y, w, h, null); graph.dispose(); bf.flush(); } /** * 根据路径获取图片 * * @param path 本地路径 or 网络地址 * @return 图片 * @throws IOException */ public static BufferedImage getImageByPath(String path) throws IOException { if (path.startsWith("http")) { // 从网络获取logo return ImageIO.read(new URL(path)); // return ImageIO.read(HttpUtil.downFile(path)); } else if (path.startsWith("/")) { // 绝对地址获取logo return ImageIO.read(new File(path)); } else { // 从资源目录下获取logo return ImageIO.read(ImageUtil.class.getClassLoader().getResourceAsStream(path)); } } /** * fixme 边框的计算需要根据最终生成logo图片的大小来定义,这样才不会出现不同的logo原图,导致边框不一致的问题 * <p> * 生成圆角图片 & 圆角边框 * * @param image 原图 * @param logoStyle 圆角的角度 * @param size 边框的边距 * @param color 边框的颜色 * @return 返回带边框的圆角图 */ public static BufferedImage makeRoundBorder(BufferedImage image, LogoStyle logoStyle, int size, Color color) { // 将图片变成圆角 int cornerRadius = 0; if (logoStyle == LogoStyle.ROUND) { cornerRadius = 30; image = makeRoundedCorner(image, cornerRadius); } int borderSize = size; int w = image.getWidth() + borderSize; int h = image.getHeight() + borderSize; BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = output.createGraphics(); g2.setComposite(AlphaComposite.Src); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(color == null ? Color.WHITE : color); g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius, cornerRadius)); // ... then compositing the image on top, // using the white shape from above as alpha source g2.setComposite(AlphaComposite.SrcAtop); g2.drawImage(image, size, size, null); g2.dispose(); return output; } /** * 生成圆角图片 * * @param image 原始图片 * @param cornerRadius 圆角的弧度 * @return 返回圆角图 */ public static BufferedImage makeRoundedCorner(BufferedImage image, int cornerRadius) { int w = image.getWidth(); int h = image.getHeight(); BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = output.createGraphics(); // This is what we want, but it only does hard-clipping, i.e. aliasing // g2.setClip(new RoundRectangle2D ...) // so instead fake soft-clipping by first drawing the desired clip shape // in fully opaque white with antialiasing enabled... g2.setComposite(AlphaComposite.Src); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(Color.WHITE); g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius, cornerRadius)); // ... then compositing the image on top, // using the white shape from above as alpha source g2.setComposite(AlphaComposite.SrcAtop); g2.drawImage(image, -5, -5, null); g2.dispose(); return output; } }
除生成类,其它都是工具类。
就到这儿吧!
还差一个logoStyle的eNum
public enum LogoStyle { ROUND, NORMAL; }