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);
//绘制logo
drawLogo(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();
//创建二维码最终Image
BufferedImage 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 barcode
for (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
*/
@Data
static 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全栈行动派";
/**
* 普通二维码
*/
@Test
public void test1() {
CodeUtil.generate(content, 300, 300,
new File(SAVE_FILE_PATH + "gen/test1.png"));
}
/**
* 带logo
*/
@Test
public 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
*/
@Test
public 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
*/
@Test
public 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"));
}
/**
* 圆角二维码
*/
@Test
public 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"));
}
/**
* 背景颜色
*/
@Test
public 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"));
}
/**
* 设置背景图片
*/
@Test
public 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"));
}
/**
* 设置上注释
*/
@Test
public 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"));
}
/**
* 设置上注释 + 背景颜色+ 字颜色
*/
@Test
public 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"));
}
/**
* 设置下注释
*/
@Test
public 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"));
}
/**
* 设置码点颜色
*/
@Test
public 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"));
}
/**
* 设置码眼颜色
*/
@Test
public 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"));
}
/**
* 设置码点形状
*/
@Test
public 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"));
}
/**
* 设置码眼形状
*/
@Test
public 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 。