1、概述

作为Java开发人员,说到生成二维码就会想到zxing开源二维码图像处理库,不可否认的是zxing确实很强大,但是实际需求中会遇到各种各样的需求是zxing满足不了的,于是就有了想法自己扩展zxing满足历史遇到的各种需求,经过3周的研究学习+开发,兼容你所有需求的Java二维码生成器孕育而生,接下来我们就看看我写的这个二维码生成器都实现了哪些功能。

2、已实现需求

  • zxing支持的二维码设置

  • 自定义logo,自动增加白边框、圆形、圆角矩形

  • 最终二维码圆角矩形生成

  • 自定义背景颜色

  • 自定义背景图片,可以设置二维码放置背景图片的位置

  • 自定义设置二维码顶部注释,自定义字体、字体颜色、背景颜色,居中、自动换行

  • 自定义设置二维码底部注释,自定义字体、字体颜色、背景颜色,居中、自动换行

  • 设置码点颜色、形状(普通、原型、方形、三角形、圆角矩形、菱形)

  • 设置码眼外框颜色、形状(方形、圆形-粗边、圆形-细边、圆角矩形-粗边、圆角矩形-中边、圆角矩形-细边)

  • 设置码眼内颜色、形状(方形、圆形、圆角矩形、三角形、菱形)

  • 其他继续扩展中

3、项目配置详细说明

3.1、二维码背景配置

package com.faea.qrcode.config;import lombok.Getter;import java.awt.*;/*** 二维码背景配置** @author liuchao* @date 2023/2/17*/
@Getter
public class CodeBackgroundConfig {public CodeBackgroundConfig() {this.color = Color.WHITE;}/*** 背景色,默认白色*/public Color color;/*** 二维码绘制在背景图上的X轴*/public int codeDrawX;/*** 二维码绘制在北京图上的Y轴*/public int codeDrawY;/*** 背景图片*/private Image image;public CodeBackgroundConfig color(Color color) {this.color = color;return this;}public CodeBackgroundConfig image(Image image) {this.image = image;return this;}public CodeBackgroundConfig codeDrawX(Integer codeDrawX) {this.codeDrawX = codeDrawX;return this;}public CodeBackgroundConfig codeDrawY(Integer codeDrawY) {this.codeDrawY = codeDrawY;return this;}
}

3.2、二维码码点配置

package com.faea.qrcode.config;import com.faea.qrcode.constants.CodeDotShapeEnum;
import lombok.Getter;import java.awt.*;/*** 二维码码点配置** @author liuchao* @date 2023/2/17*/
@Getter
public class CodeDotConfig {public CodeDotConfig() {this.color = Color.BLACK;this.shape = CodeDotShapeEnum.NORMAL;}/*** 码点颜色*/private Color color;/*** 形状枚举*/private CodeDotShapeEnum shape;public CodeDotConfig shape(CodeDotShapeEnum shape) {this.shape = shape;return this;}public CodeDotConfig color(Color color) {this.color = color;return this;}
}

3.3、二维码码眼内配置

package com.faea.qrcode.config;import com.faea.qrcode.constants.CodeEyeInsideShapeEnum;
import lombok.Getter;import java.awt.*;/*** 二维码码眼内配置** @author liuchao* @date 2023/2/19*/
@Getter
public class CodeEyeInsideConfig {public CodeEyeInsideConfig() {this(Color.BLACK, CodeEyeInsideShapeEnum.SQUARE);}public CodeEyeInsideConfig(Color color, CodeEyeInsideShapeEnum shape) {this.color = color;this.shape = shape;}/*** 颜色*/private Color color;/*** 形状*/private CodeEyeInsideShapeEnum shape;public CodeEyeInsideConfig color(Color color) {this.color = color;return this;}public CodeEyeInsideConfig shape(CodeEyeInsideShapeEnum shape) {this.shape = shape;return this;}
}

3.4、二维码码眼外配置

package com.faea.qrcode.config;import com.faea.qrcode.constants.CodeEyeOutShapeEnum;
import lombok.Getter;import java.awt.*;/*** 二维码码眼外配置** @author liuchao* @date 2023/2/19*/
@Getter
public class CodeEyeOutConfig {public CodeEyeOutConfig() {this(Color.BLACK, CodeEyeOutShapeEnum.SQUARE);}public CodeEyeOutConfig(Color color, CodeEyeOutShapeEnum shape) {this.color = color;this.shape = shape;}/*** 颜色*/private Color color;/*** 形状*/private CodeEyeOutShapeEnum shape;public CodeEyeOutConfig color(Color color) {this.color = color;return this;}public CodeEyeOutConfig shape(CodeEyeOutShapeEnum shape) {this.shape = shape;return this;}
}

3.5、二维码LOGO配置

package com.faea.qrcode.config;
import com.faea.qrcode.constants.CodeLogoShapeEnum;
import lombok.Data;import java.awt.*;
import java.io.File;/*** 二维码LOGO配置** @author liuchao* @date 2023/2/17*/
@Data
public class CodeLogoConfig {public CodeLogoConfig() {this(null);}public CodeLogoConfig(Image image) {this.image = image;this.scale = 0.1F;this.shape = CodeLogoShapeEnum.ROUNDED;this.alpha = 1F;}/*** LOGO 图片*/private Image image;/*** 形状*/private CodeLogoShapeEnum shape;/*** LOGO 透明度*/private Float alpha;/*** 缩放比例。比例大于1时为放大,小于1大于0为缩小*/private float scale;/*** 设置二维码中的Logo文件** @param logoPath 二维码中的Logo路径* @return this;*/public CodeLogoConfig image(String logoPath) {return image(FileUtil.file(logoPath));}/*** 设置二维码中的Logo文件** @param logoFile 二维码中的Logo* @return this;*/public CodeLogoConfig image(File logoFile) {return image(ImgUtil.read(logoFile));}/*** 设置二维码中的Logo** @param image 二维码中的Logo* @return this;*/public CodeLogoConfig image(Image image) {this.image = image;return this;}public CodeLogoConfig shape(CodeLogoShapeEnum shape) {this.shape = shape;return this;}public CodeLogoConfig alpha(Float alpha) {this.alpha = alpha;return this;}public CodeLogoConfig scale(float scale) {this.scale = scale;return this;}
}

3.6、二维码注释配置

package com.faea.qrcode.config;import lombok.Data;import java.awt.*;/*** 二维码注释配置** @author liuchao* @date 2023/2/17*/
@Data
public class CodeNoteConfig {public CodeNoteConfig() {this.rowSpacing = 5;this.size = 14;this.font = "SimHei";this.backColor = Color.DARK_GRAY;this.fontColor = Color.BLACK;}/*** 注释内容*/private String content;/*** 注释字体*/private String font;/*** 字体大小*/private Integer size;/*** 背景颜色*/private Color backColor;/*** 字体颜色*/private Color fontColor;/*** 行间距*/private Integer rowSpacing;public CodeNoteConfig backColor(Color color) {this.backColor = color;return this;}public CodeNoteConfig fontColor(Color color) {this.fontColor = color;return this;}public CodeNoteConfig content(String content) {this.content = content;return this;}public CodeNoteConfig font(String font) {this.font = font;return this;}public CodeNoteConfig size(Integer size) {this.size = size;return this;}public CodeNoteConfig rowSpacing(Integer rowSpacing) {this.rowSpacing = rowSpacing;return this;}
}

3.7、总配置入口类

package com.faea.qrcode.config;import cn.hutool.core.util.CharsetUtil;
import com.google.zxing.EncodeHintType;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.Getter;import java.nio.charset.Charset;
import java.util.HashMap;/*** 二维码总配置** @author liuchao* @date 2023/2/17*/
@Getter
public class CodeConfig {/*** 创建QrConfig** @return QrConfig* @since 4.1.14*/public static CodeConfig create() {return new CodeConfig();}/*** 构造,默认长宽为300*/public CodeConfig() {this(500, 500);}/*** 构造** @param width  宽* @param height 长*/public CodeConfig(int width, int height) {this.width = width;this.height = height;this.margin = 1;this.dot = new CodeDotConfig();this.background = new CodeBackgroundConfig();this.eyeOut = new CodeEyeOutConfig();this.eyeInside = new CodeEyeInsideConfig();this.errorCorrection = ErrorCorrectionLevel.H;this.charset = CharsetUtil.CHARSET_UTF_8;this.arc = 1D;}/*** 宽*/private Integer width;/*** 长*/private Integer height;/*** 边距1~4* 二维码空白区域,最小为0也有白边,只是很小,最小是6像素左右*/private Integer margin;/*** 码眼外*/private CodeEyeOutConfig eyeOut;/*** 码眼内*/private CodeEyeInsideConfig eyeInside;/*** 纠错级别*/private ErrorCorrectionLevel errorCorrection;/*** 编码*/private Charset charset;/*** 前景色配置*/private CodeDotConfig dot;/*** 背景色配置*/private CodeBackgroundConfig background;/*** 圆角弧度,0~1,为长宽占比* 设置最终二维码 圆角弧度* 1:原图* 0.3D:圆角矩形*/private Double arc;/*** logo*/private CodeLogoConfig logo;/*** 顶部注释*/private CodeNoteConfig topNote;/*** 底部注释*/private CodeNoteConfig bottomNote;public CodeConfig logo(CodeLogoConfig logo) {this.logo = logo;return this;}/*** 转换为Zxing的二维码配置** @return 配置*/public HashMap<EncodeHintType, Object> toHints() {// 配置final HashMap<EncodeHintType, Object> hints = new HashMap<>();if (null != this.charset) {hints.put(EncodeHintType.CHARACTER_SET, charset.toString().toLowerCase());}if (null != this.errorCorrection) {hints.put(EncodeHintType.ERROR_CORRECTION, this.errorCorrection);}if (null != this.margin) {hints.put(EncodeHintType.MARGIN, this.margin);}return hints;}public CodeConfig width(Integer width) {this.width = width;return this;}public CodeConfig height(Integer height) {this.height = height;return this;}public CodeConfig margin(Integer margin) {this.margin = margin;return this;}public CodeConfig eyeOut(CodeEyeOutConfig eyeOut) {this.eyeOut = eyeOut;return this;}public CodeConfig eyeInside(CodeEyeInsideConfig eyeInside) {this.eyeInside = eyeInside;return this;}public CodeConfig errorCorrection(ErrorCorrectionLevel errorCorrection) {this.errorCorrection = errorCorrection;return this;}public CodeConfig charset(Charset charset) {this.charset = charset;return this;}public CodeConfig dot(CodeDotConfig dot) {this.dot = dot;return this;}public CodeConfig background(CodeBackgroundConfig background) {this.background = background;return this;}public CodeConfig topNote(CodeNoteConfig topNote) {this.topNote = topNote;return this;}public CodeConfig bottomNote(CodeNoteConfig bottomNote) {this.bottomNote = bottomNote;return this;}public CodeConfig arc(Double arc) {this.arc = arc;return this;}
}

3.8、核心工具类

package com.faea.qrcode.util;import com.faea.qrcode.config.*;
import com.faea.qrcode.constants.CodeEyePositionEnum;
import com.faea.qrcode.constants.CodeLogoShapeEnum;
import com.faea.qrcode.model.CodeDotDrawModel;
import com.faea.qrcode.model.CodeEyeInsideDrawModel;
import com.faea.qrcode.model.CodeEyeOutDrawModel;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import lombok.Data;
import lombok.extern.java.Log;import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.OutputStream;/*** 二维码工具类** @author liuchao* @date 2023/2/17*/
@Log
public class CodeUtil {/*** 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示** @param content    内容* @param config     二维码配置,包括长、宽、边距、颜色等* @param imageType  图片类型(图片扩展名),见{@link ImgUtil}* @param logoBase64 logo 图片的 base64 编码* @return 图片 Base64 编码字符串*/public static String generateAsBase64(String content, CodeConfig config, String imageType, String logoBase64) {return generateAsBase64(content, config, imageType, Base64.decode(logoBase64));}/*** 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示** @param content   内容* @param config    二维码配置,包括长、宽、边距、颜色等* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}* @param logo      logo 图片的byte[]* @return 图片 Base64 编码字符串*/public static String generateAsBase64(String content, CodeConfig config, String imageType, byte[] logo) {return generateAsBase64(content, config, imageType, ImgUtil.toImage(logo));}/*** 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示** @param content   内容* @param config    二维码配置,包括长、宽、边距、颜色等* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}* @param logo      logo 图片的byte[]* @return 图片 Base64 编码字符串*/public static String generateAsBase64(String content, CodeConfig config, String imageType, Image logo) {config.getLogo().image(logo);return generateAsBase64(content, config, imageType);}/*** 生成 Base64 编码格式的二维码,以 String 形式表示** <p>* 输出格式为: data:image/[type];base64,[data]* </p>** @param content   内容* @param config    二维码配置,包括长、宽、边距、颜色等* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}* @return 图片 Base64 编码字符串*/public static String generateAsBase64(String content, CodeConfig config, String imageType) {final BufferedImage img = generate(content, config);return ImgUtil.toBase64DateUri(img, imageType);}/*** 生成PNG格式的二维码图片,以byte[]形式表示** @param content 内容* @param width   宽度* @param height  高度* @return 图片的byte[]*/public static byte[] generatePng(String content, int width, int height) {final ByteArrayOutputStream out = new ByteArrayOutputStream();generate(content, width, height, ImgUtil.IMAGE_TYPE_PNG, out);return out.toByteArray();}/*** 生成PNG格式的二维码图片,以byte[]形式表示** @param content 内容* @param config  二维码配置,包括长、宽、边距、颜色等* @return 图片的byte[]*/public static byte[] generatePng(String content, CodeConfig config) {final ByteArrayOutputStream out = new ByteArrayOutputStream();generate(content, config, ImgUtil.IMAGE_TYPE_PNG, out);return out.toByteArray();}/*** 生成二维码到文件,二维码图片格式取决于文件的扩展名** @param content    文本内容* @param width      宽度* @param height     高度* @param targetFile 目标文件,扩展名决定输出格式* @return 目标文件*/public static File generate(String content, int width, int height, File targetFile) {final BufferedImage image = generate(content, width, height);ImgUtil.write(image, targetFile);return targetFile;}/*** 生成二维码到文件,二维码图片格式取决于文件的扩展名** @param content    文本内容* @param width      宽度* @param height     高度* @param logo       logo图片* @param targetFile 目标文件,扩展名决定输出格式* @return 目标文件*/public static File generate(String content, int width, int height, Image logo, File targetFile) {CodeConfig config = new CodeConfig(width, height).logo(new CodeLogoConfig(logo));final BufferedImage image = generate(content, config);ImgUtil.write(image, targetFile);return targetFile;}/*** 生成二维码到输出流** @param content   文本内容* @param width     宽度* @param height    高度* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}* @param out       目标流*/public static void generate(String content, int width, int height, String imageType, OutputStream out) {final BufferedImage image = generate(content, width, height);ImgUtil.write(image, imageType, out);}/*** 生成二维码到输出流** @param content   文本内容* @param config    二维码配置,包括长、宽、边距、颜色等* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}* @param out       目标流*/public static void generate(String content, CodeConfig config, String imageType, OutputStream out) {final BufferedImage image = generate(content, config);ImgUtil.write(image, imageType, out);}/*** 生成二维码图片** @param content 文本内容* @param width   宽度* @param height  高度* @return 二维码图片(黑白)*/public static BufferedImage generate(String content, int width, int height) {return generate(content, new CodeConfig(width, height));}/*** 生成二维码到文件,二维码图片格式取决于文件的扩展名** @param content    文本内容* @param config     二维码配置,包括长、宽、边距、颜色等* @param targetFile 目标文件,扩展名决定输出格式* @return 目标文件*/public static File generate(String content, CodeConfig config, File targetFile) {final BufferedImage image = generate(content, config);ImgUtil.write(image, targetFile);return targetFile;}/*** 生成二维码图片** @param content 二维码内容* @param config  二维码配置* @return java.awt.image.BufferedImage* @author liuchao* @date 2023/2/17*/public static BufferedImage generate(String content, CodeConfig config) {BitMatrix bitMatrix;try {bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE,config.getWidth(), config.getHeight(), config.toHints());} catch (WriterException e) {throw new RuntimeException(e);}//获取二维码图片尺寸信息CodeImageSizeInfo sizeInfo = convertCodeImageSizeInfo(config);BufferedImage image = toImage(bitMatrix, sizeInfo, config);//绘制背景图片image = drawBackgroundImage(image, config, sizeInfo);//绘制logodrawLogo(image, config);//圆角处理if (config.getArc() < 1D) {image = ImgUtil.toBufferedImage(Img.from(image).round(config.getArc()).getImg());}return image;}/*** 通过内容配置转换为字体** @param note* @return java.awt.FontMetrics* @author liuchao* @date 2023/2/17*/private static FontMetrics convertFont(CodeNoteConfig note) {if (null == note) {return null;}Font font = new Font(note.getFont(), Font.PLAIN, note.getSize());JLabel jLabel = new JLabel(note.getContent());//第一次获取非常慢,建议在应用启动的时候加载一次FontMetrics fontMetrics = jLabel.getFontMetrics(font);return fontMetrics;}/*** BitMatrix转BufferedImage** @param matrix   BitMatrix* @param sizeInfo 二维码尺寸信息* @param config* @return java.awt.image.BufferedImage* @author liuchao* @date 2023/2/17*/private static BufferedImage toImage(BitMatrix matrix, CodeImageSizeInfo sizeInfo, CodeConfig config) {final int width = matrix.getWidth();//创建二维码最终ImageBufferedImage image = new BufferedImage(width, sizeInfo.getCodeImageTotalHeight(), BufferedImage.TYPE_INT_ARGB);Graphics2D graphics2D = ImageUtil.imageToGraphics2D(image);//绘制背景drawBackgroundColor(graphics2D, sizeInfo, config, matrix);// 处理上、下注释、码点、码眼drawNoteAndDotEye(graphics2D, sizeInfo, config, matrix);//添加注释drawNote(graphics2D, sizeInfo, config);graphics2D.dispose();image.flush();return image;}/*** 绘制logo** @param image* @param config* @return void* @author liuchao* @date 2023/2/18*/private static void drawLogo(BufferedImage image, CodeConfig config) {if (null == config.getLogo() || null == config.getLogo().getImage()) {return;}CodeLogoConfig logoConfig = config.getLogo();//圆角弧度,0~1,为长宽占比Double arc = logoConfig.getShape().getArc();//缩放BufferedImage srcImage = ImgUtil.toBufferedImage(Img.from(logoConfig.getImage()).scale(logoConfig.getScale()).round(arc).getImg());//增加边框 非原型 则增加边框if (!logoConfig.getShape().equals(CodeLogoShapeEnum.ORIGINAL)) {srcImage = ImageUtil.roundBorder(srcImage, arc);}ImgUtil.pressImage(image, srcImage, new Rectangle(srcImage.getWidth(), srcImage.getHeight()), logoConfig.getAlpha());}/*** 增加注释** @param graphics2D* @param sizeInfo* @param config* @return void* @author liuchao* @date 2023/2/18*/private static void drawNote(Graphics2D graphics2D, CodeImageSizeInfo sizeInfo, CodeConfig config) {if (sizeInfo.getTopNoteHeight() == 0 && sizeInfo.getBottomNoteHeight() == 0) {return;}int startWriteY = 0;//顶部注释if (sizeInfo.getTopNoteHeight() > 0) {CodeNoteConfig topNode = config.getTopNote();drawHorizontalWords(graphics2D, startWriteY, topNode.getContent(), sizeInfo.topNoteFontMetrics, topNode.getFontColor(),config.getWidth(), sizeInfo.getTopNoteRowSize(),topNode.getRowSpacing());startWriteY += sizeInfo.getTopNoteHeight();}//底部注释if (sizeInfo.getBottomNoteHeight() > 0) {startWriteY += config.getHeight();CodeNoteConfig bottomNote = config.getBottomNote();drawHorizontalWords(graphics2D, startWriteY, bottomNote.getContent(), sizeInfo.bottomNoteFontMetrics, bottomNote.getFontColor(),config.getWidth(), sizeInfo.getTopNoteRowSize(),bottomNote.getRowSpacing());}}/*** 在Graphics2D 写横向文字** @param graphics2D* @param startWriteY* @param content* @param fontMetrics* @param fontColor   字体颜色* @param qrCodeWidth* @param rowSize* @param rowSpacing* @return void* @author liuchao* @date 2023/2/18*/private static void drawHorizontalWords(Graphics2D graphics2D, int startWriteY, String content,FontMetrics fontMetrics, Color fontColor, int qrCodeWidth,int rowSize, int rowSpacing) {graphics2D.setFont(fontMetrics.getFont());graphics2D.setColor(fontColor);int rowHeight = fontMetrics.getHeight();//当前写入Y轴int curWriteY = startWriteY + rowHeight;if (rowSize > 1) {//当前行字符StringBuffer curRowWords = new StringBuffer();for (int i = 0; i < content.length(); i++) {curRowWords.append(content.charAt(i));if (i == (content.length() - 1)) {int wordWidth = fontMetrics.stringWidth(curRowWords.toString());int writeX = (qrCodeWidth - wordWidth) / 2;graphics2D.drawString(curRowWords.toString(), writeX, curWriteY);} else {//判断当前已累加字符+下一个字符  长度是否大于 总宽度StringBuffer tempWords = new StringBuffer(curRowWords).append(content.charAt(i + 1));if (fontMetrics.stringWidth(tempWords.toString()) > qrCodeWidth) {int wordWidth = fontMetrics.stringWidth(curRowWords.toString());int writeX = (qrCodeWidth - wordWidth) / 2;graphics2D.drawString(curRowWords.toString(), writeX, curWriteY);curWriteY += rowHeight + rowSpacing;curRowWords.delete(0, curRowWords.length());//最后一个字符了}}}} else {int wordWidth = fontMetrics.stringWidth(content);int writeX = (qrCodeWidth - wordWidth) / 2;graphics2D.drawString(content, writeX, curWriteY);}}/*** 绘制注释+码点+码眼** @param graphics2D* @param sizeInfo* @param config* @param matrix* @return void* @author liuchao* @date 2023/2/22*/private static void drawNoteAndDotEye(Graphics2D graphics2D,CodeImageSizeInfo sizeInfo, CodeConfig config, BitMatrix matrix) {CodeDotConfig dot = config.getDot();final int height = matrix.getHeight();final int width = matrix.getWidth();Boolean leftTopOut = false;Boolean rightTopOut = false;Boolean leftBottomOut = false;Boolean leftTopInside = false;Boolean rightTopInside = false;Boolean leftBottomInside = false;//上注释if (sizeInfo.getTopNoteHeight() > 0) {CodeNoteConfig topNote = config.getTopNote();graphics2D.setColor(topNote.getBackColor());graphics2D.fillRect(0, 0, width, sizeInfo.getTopNoteHeight());}//下注释if (sizeInfo.getBottomNoteHeight() > 0) {CodeNoteConfig bottomNote = config.getBottomNote();graphics2D.setColor(bottomNote.getBackColor());graphics2D.fillRect(0, sizeInfo.getTopNoteHeight() + height, width, sizeInfo.getBottomNoteHeight());}int multiple = matrix.getMultiple();int leftPadding = matrix.getLeftPadding();int topPadding = matrix.getTopPadding();ByteMatrix input = matrix.getInputMatrix();for (int inputY = 0, outputY = topPadding; inputY < input.getHeight(); inputY++, outputY += multiple) {// Write the contents of this row of the barcodefor (int inputX = 0, outputX = leftPadding; inputX < input.getWidth(); inputX++, outputX += multiple) {if (input.get(inputX, inputY) == 1) {int y = outputY + sizeInfo.getTopNoteHeight();// 左上角码眼,外边框if (input.getEyeOutPoint(CodeEyePositionEnum.LEFT_TOP, inputX, inputY) == 1) {if (!leftTopOut) {config.getEyeOut().getShape().draw(graphics2D, CodeEyeOutDrawModel.create(config.getEyeOut().getColor(), outputX, y, multiple));leftTopOut = true;}//右上角码眼,外边框} else if (input.getEyeOutPoint(CodeEyePositionEnum.RIGHT_TOP, inputX, inputY) == 1) {if (!rightTopOut) {config.getEyeOut().getShape().draw(graphics2D, CodeEyeOutDrawModel.create(config.getEyeOut().getColor(), outputX, y, multiple));rightTopOut = true;}//左下角码眼,外边框} else if (input.getEyeOutPoint(CodeEyePositionEnum.LEFT_BOTTOM, inputX, inputY) == 1) {if (!leftBottomOut) {config.getEyeOut().getShape().draw(graphics2D, CodeEyeOutDrawModel.create(config.getEyeOut().getColor(), outputX, y, multiple));leftBottomOut = true;}// 左上角码眼,内边框} else if (input.getEyeInsidePoint(CodeEyePositionEnum.LEFT_TOP, inputX, inputY) == 1) {if (!leftTopInside) {config.getEyeInside().getShape().draw(graphics2D, CodeEyeInsideDrawModel.create(config.getEyeInside().getColor(), outputX, y, multiple));leftTopInside = true;}// 右上角码眼,内边框} else if (input.getEyeInsidePoint(CodeEyePositionEnum.RIGHT_TOP, inputX, inputY) == 1) {if (!rightTopInside) {config.getEyeInside().getShape().draw(graphics2D, CodeEyeInsideDrawModel.create(config.getEyeInside().getColor(), outputX, y, multiple));rightTopInside = true;}// 左下角码眼,内边框} else if (input.getEyeInsidePoint(CodeEyePositionEnum.LEFT_BOTTOM, inputX, inputY) == 1) {if (!leftBottomInside) {config.getEyeInside().getShape().draw(graphics2D, CodeEyeInsideDrawModel.create(config.getEyeInside().getColor(), outputX, y, multiple));leftBottomInside = true;}} else {// 其他黑色区域dot.getShape().draw(graphics2D, new CodeDotDrawModel(dot.getColor(), outputX, y, multiple, multiple));}}}}}/*** 绘制背景图片** @param codeImage* @param config* @param sizeInfo  尺寸信息* @return java.awt.image.BufferedImage* @author liuchao* @date 2023/2/22*/private static BufferedImage drawBackgroundImage(BufferedImage codeImage, CodeConfig config, CodeImageSizeInfo sizeInfo) {CodeBackgroundConfig background = config.getBackground();if (ObjectUtil.isEmpty(background.getImage())) {return codeImage;}final int codeWidth = codeImage.getWidth();final int codeHeight = codeImage.getHeight();BufferedImage backgroundImage = ImgUtil.toBufferedImage(background.getImage());int imageW = backgroundImage.getWidth(null);int imageH = backgroundImage.getHeight(null);//背景图的宽高不能小于  二维码宽高int w = imageW < config.getWidth() ? config.getWidth() : imageW;int h = imageH < config.getHeight() ? config.getHeight() : imageH;//如果绘制X\Y都等于0,则直接填充if (background.getCodeDrawX() == 0 && background.getCodeDrawY() == 0) {w = config.getWidth();h = config.getHeight() + sizeInfo.getTopNoteHeight() + sizeInfo.getBottomNoteHeight();}// 背景图缩放if (imageW != w || imageH != h) {BufferedImage newBackgroundImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);newBackgroundImage.getGraphics().drawImage(backgroundImage.getScaledInstance(w, h, Image.SCALE_SMOOTH), 0, 0, null);backgroundImage = newBackgroundImage;}Graphics2D graphics2D = ImageUtil.imageToGraphics2D(backgroundImage);int codeDrawX = (w - codeWidth) >> 1;int codeDrawY = (h - codeHeight) >> 1;//如果设置了开始绘制的坐标,则按照这个坐标绘制if (background.getCodeDrawX() != 0 || background.getCodeDrawY() != 0) {codeDrawX = background.getCodeDrawX();codeDrawY = background.getCodeDrawY();}graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1F));graphics2D.drawImage(codeImage.getScaledInstance(codeWidth, codeHeight, Image.SCALE_SMOOTH), codeDrawX, codeDrawY,null);graphics2D.dispose();backgroundImage.flush();return backgroundImage;}/*** 绘制背景图片** @param graphics2D* @param sizeInfo   尺寸信息* @param config     配置信息* @return void* @author liuchao* @date 2023/2/21*/private static void drawBackgroundColor(Graphics2D graphics2D, CodeImageSizeInfo sizeInfo, CodeConfig config, BitMatrix matrix) {CodeBackgroundConfig background = config.getBackground();//如果设置了背景图片,则忽略背景色if (ObjectUtil.isNotEmpty(background.getImage())) {return;}//设置二维码区域背景颜色  全部为设置的背景色graphics2D.setColor(background.getColor());graphics2D.fillRect(0, sizeInfo.getTopNoteHeight(), matrix.getWidth(), matrix.getHeight());}/*** 转换二维码最终图片尺寸信息** @param config 二维码配置信息* @return com.faea.qrcode.util.CodeUtil.CodeImageSizeInfo* @author liuchao* @date 2023/2/18*/private static CodeImageSizeInfo convertCodeImageSizeInfo(CodeConfig config) {int qrCodeWidth = config.getWidth();int qrCodeHeight = config.getHeight();CodeImageSizeInfo info = new CodeImageSizeInfo();//二维码上注释Integer topNoteHeight = 0;CodeNoteConfig topNote = config.getTopNote();if (null != topNote) {FontMetrics topNoteFont = convertFont(topNote);int topNoteRowHeight = topNoteFont.getHeight();int topNoteWidth = topNoteFont.stringWidth(topNote.getContent());if (topNoteWidth > qrCodeWidth) {int topNoteRowSize = topNoteWidth / qrCodeWidth;if (topNoteWidth % qrCodeWidth != 0) {topNoteRowSize += 1;}info.setTopNoteRowSize(topNoteRowSize);topNoteHeight += (topNote.getRowSpacing() + topNoteRowHeight) * topNoteRowSize;} else {topNoteHeight += (topNote.getRowSpacing() + topNoteRowHeight);}info.setTopNoteHeight(topNoteHeight);info.setTopNoteFontMetrics(topNoteFont);}CodeNoteConfig bottomNote = config.getBottomNote();Integer bottomNoteHeight = 0;if (null != bottomNote) {//二维码下注释FontMetrics bottomNoteFont = convertFont(bottomNote);int bottomNoteRowHeight = bottomNoteFont.getHeight();int bottomNoteWidth = bottomNoteFont.stringWidth(bottomNote.getContent());if (bottomNoteWidth > qrCodeWidth) {int bottomNoteRowSize = bottomNoteWidth / qrCodeWidth;if (bottomNoteWidth % qrCodeWidth != 0) {bottomNoteRowSize += 1;}info.setBottomNoteRowSize(bottomNoteRowSize);bottomNoteHeight += (bottomNote.getRowSpacing() + bottomNoteRowHeight) * bottomNoteRowSize;} else {bottomNoteHeight += (bottomNote.getRowSpacing() + bottomNoteRowHeight);}info.setBottomNoteHeight(bottomNoteHeight);info.setBottomNoteFontMetrics(bottomNoteFont);}info.setCodeImageTotalHeight(topNoteHeight + qrCodeHeight + bottomNoteHeight);return info;}/*** 二维码尺寸信息** @author liuchao* @date 2023/2/18*/@Datastatic class CodeImageSizeInfo {/*** 顶部注释占用高度*/int topNoteHeight;/*** 顶部注释 字体信息*/FontMetrics topNoteFontMetrics;/*** 顶部注释占用行数*/int topNoteRowSize;/*** 底部注释占用高度*/int bottomNoteHeight;/*** 底部注释占用行数*/int bottomNoteRowSize;/*** 底部注释 字体信息*/FontMetrics bottomNoteFontMetrics;/*** 二维码图片总高度*/int codeImageTotalHeight;}}

4、效果查看

4.1、效果单元测试

总体写了14个单元测试,通过单元测试可以看出,使用起来还是很方便的。

package com.faea;import com.faea.qrcode.config.CodeConfig;
import com.faea.qrcode.config.CodeLogoConfig;
import com.faea.qrcode.config.CodeNoteConfig;
import com.faea.qrcode.constants.CodeDotShapeEnum;
import com.faea.qrcode.constants.CodeEyeInsideShapeEnum;
import com.faea.qrcode.constants.CodeEyeOutShapeEnum;
import com.faea.qrcode.constants.CodeLogoShapeEnum;
import com.faea.qrcode.util.CodeUtil;
import org.junit.Test;import java.awt.*;
import java.io.File;/*** 二维码测试** @author liuchao* @date 2023/2/23*/
public class CodeTest {/*** 保存二维码地址*/private static final String SAVE_FILE_PATH = "src/test/code/";/*** 二维码内容*/private static final String content = "https://blog.csdn.net/u011837804";/*** 注释*/private static final String note = "Java全栈行动派";/*** 普通二维码*/@Testpublic void test1() {CodeUtil.generate(content, 300, 300,new File(SAVE_FILE_PATH + "gen/test1.png"));}/*** 带logo*/@Testpublic void test2() {CodeUtil.generate(content, 300, 300,ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png")),new File(SAVE_FILE_PATH + "gen/test2.png"));}/*** 圆形logo*/@Testpublic void test3() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))).shape(CodeLogoShapeEnum.CIRCLE));CodeUtil.generate(content, config,new File(SAVE_FILE_PATH + "gen/test3.png"));}/*** 原型logo*/@Testpublic void test4() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))).shape(CodeLogoShapeEnum.ORIGINAL));CodeUtil.generate(content, config,new File(SAVE_FILE_PATH + "gen/test4.png"));}/*** 圆角二维码*/@Testpublic void test5() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png")))).arc(0.3D);CodeUtil.generate(content, config,new File(SAVE_FILE_PATH + "gen/test5.png"));}/*** 背景颜色*/@Testpublic void test6() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.getBackground().color(ImgUtil.getColor(129590));CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test6.png"));}/*** 设置背景图片*/@Testpublic void test7() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.getBackground().image(ImgUtil.read(new File(SAVE_FILE_PATH + "background.png")));CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test7.png"));}/*** 设置上注释*/@Testpublic void test8() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.topNote(new CodeNoteConfig().content(note).backColor(Color.WHITE));CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test8.png"));}/*** 设置上注释 + 背景颜色+ 字颜色*/@Testpublic void test9() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.topNote(new CodeNoteConfig().content(note).backColor(Color.WHITE));CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test9.png"));}/*** 设置下注释*/@Testpublic void test10() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.bottomNote(new CodeNoteConfig().content(note).backColor(Color.WHITE));CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test10.png"));}/*** 设置码点颜色*/@Testpublic void test11() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.bottomNote(new CodeNoteConfig().content(note).backColor(Color.WHITE).fontColor(Color.BLUE));config.getDot().color(Color.BLUE);CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test11.png"));}/*** 设置码眼颜色*/@Testpublic void test12() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.bottomNote(new CodeNoteConfig().content(note).backColor(Color.WHITE).fontColor(Color.BLUE));config.getDot().color(Color.BLUE);config.getEyeInside().color(Color.BLUE);config.getEyeOut().color(Color.RED);CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test12.png"));}/*** 设置码点形状*/@Testpublic void test13() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.bottomNote(new CodeNoteConfig().content(note).backColor(Color.WHITE).fontColor(Color.BLUE));config.getDot().color(Color.BLUE).shape(CodeDotShapeEnum.DIAMOND);config.getEyeInside().color(Color.BLUE);config.getEyeOut().color(Color.RED);CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test13.png"));}/*** 设置码眼形状*/@Testpublic void test14() {CodeConfig config = new CodeConfig(300, 300).logo(new CodeLogoConfig(ImgUtil.read(new File(SAVE_FILE_PATH + "logo.png"))));config.bottomNote(new CodeNoteConfig().content(note).backColor(Color.WHITE).fontColor(Color.BLUE));config.getDot().color(Color.BLUE).shape(CodeDotShapeEnum.TRIANGLE);config.getEyeInside().color(Color.BLUE).shape(CodeEyeInsideShapeEnum.ROUNDED);config.getEyeOut().color(Color.RED).shape(CodeEyeOutShapeEnum.ROUNDED_THICK_EDGE);CodeUtil.generate(content, config, new File(SAVE_FILE_PATH + "gen/test14.png"));}
}

4.2、生成所有的效果二维码

由于文章审核要求,所以做了图片模糊,但是二维码优化效果还是可以看清楚的。

不可否认的是要实现这些功能,需要对二维码生成原理、zxing源码等非常属性,着实让小编下了很大的功能,从上面小编贴出来的代码可以看出针对每个设置项注释是非常到位的,核心工具类也贴出来了,希望对朋友们的开发有帮助,全部源码下载地址:https://download.csdn.net/download/u011837804/87490526 。

Java自定义生成二维码(兼容你所有的需求)相关推荐

  1. java实现生成二维码及扫码登录

    java实现生成二维码及扫码登录 1. 场景描述 2. 实现思路 3. 代码实现过程 3.1 pom.xml 3.2 二维码工具类 3.3 生成二维码并下载为图片 3.4 扫码登录 1. 场景描述   ...

  2. vue2.0 自定义 生成二维码(QRCode)组件

    1.自定义 生成二维码组件 QRCode.vue <!-- 生成二维码 组件 --> <template><canvasclass="qrcode-canvas ...

  3. 在java中生成二维码,并直接输出到jsp页面

    在java中生成的二维码不存到磁盘里要直接输出到页面上,这就需要把生成的二维码直接以流的形式输出到页面上,我用的是myeclipse 和 tomcat 它的原理是:在加载页面时,根据img的src(c ...

  4. Java解析生成二维码-log

    Java解析生成二维码 1.pom.xml依赖 <!-- 引入二维码相关的依赖--><dependency><groupId>com.google.zxing< ...

  5. Java实现生成二维码(内嵌LOGO和无LOGO均可)扫描进入目标网址

    效果图: <<<<<< = = = = = = >>>>>> 1.导入maven依赖 <dependency>< ...

  6. java springboot生成二维码图片

    java生成二维码图片 Maven依赖 <!--生成二维码--> <dependency><groupId>com.google.zxing</groupId ...

  7. java springMVC生成二维码

    Zxing是Google提供的工具,提供了二维码的生成与解析的方法,现在使用Java利用Zxing生成二维码 1),二维码的生成 将Zxing-core.jar 包加入到classpath下. 我的下 ...

  8. java零碎要点---用java实现生成二维码,与解析代码实现

    创梦综合技术qq交流群:CreDream:251572072 二维码,是一种采用黑白相间的平面几何图形通过相应的编码算法来记录文字.图片.网址等信息的条码图片.如下图 二维码的特点: 1.  高密度编 ...

  9. java后台生成二维码以及页面显示二维码方式

    上篇文章已经说明并发布了后台生成二维码工具类,大家可以直接去看或者去拿. 地址:最简单实用的java生成二维码工具 现在呢说明页面上展示二维码的两种方式: 1.使用img标签的src来请求生成二维码, ...

最新文章

  1. linux系统获取用户信息失败怎么办,异常详细信息: System.Data.SqlClient.SqlException: 用户 'NT AUTHORITY\IUSR' 登录失败解决办法...
  2. 再用Udp--聊天工具
  3. java equal 不等于_java Integer判断相等只能使用equals(不能使用==)
  4. ESP32 OTA升级框架
  5. Android插件化原理解析——Hook机制之Binder Hook
  6. 基于锁相环的定时误差调整
  7. Visual Studio下的 JS CSS 压缩和编辑插件
  8. 打造IOS移动***测试平台
  9. 安卓智能手机刷机前的准备工作
  10. pos费率怎么用计算机计算,刷卡机费率怎么算出来的(简单有效的方法)
  11. 2021年第十二届蓝桥杯javaA组省赛
  12. C++解压KRC文件
  13. 从计算机移到u盘如何加快速度,加快U盘写入速度
  14. 走楼梯c语言程序,动态规划走楼梯
  15. 企业研发人员配备比例_高新企业科技人员占比是什么要求
  16. CF卡弹出磁盘结构损坏且无法读取实测解决教程
  17. 重磅报告 | 《中国企业2020:人工智能应用实践与趋势》
  18. matlab rgb转色温,如何将RGB值转换为色温?
  19. 详解浅拷贝,深拷贝及实现方法
  20. 电动汽车充电站的最优选址matlab程序

热门文章

  1. Windows基础命令
  2. 9月17日服务器维护,2009年9月17日维护公告
  3. 同程网格版式设计代码页
  4. UML常见依赖关系图
  5. 【贪心算法】会场安排问题
  6. vb动态加入ShockwaveFlash后怎么用ShockwaveFlash1_FSCommand?
  7. window onload
  8. Flutter MaterialApp和Scaffold
  9. 数学物理方程学习笔记1
  10. 被中国人误传了数千年的七句话 (转)