需求: 类似于做一个码牌, UI提供背景图模板, 后端将二维码合成到背景图模板中
背景图模板 最终效果图
开撸
package cc.sunni;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import org.springframework.core.io.ClassPathResource;
import sun.misc.BASE64Encoder;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* 码牌: 将二维码合成到背景图片中
*
* @author 江黎
* @since 2022-07-21
*/
public class QrCodeCard {
/**
* 二维码的宽度
*/
private static final int QR_CODE_WIDTH = 640;
/**
* 二维码的高度
*/
private static final int QR_CODE_HEIGHT = 640;
/**
* title字体
*/
private static final Font TITLE_FONT = new Font("宋体", Font.BOLD, 30);
/**
* desc字体
*/
private static final Font DESC_FONT = new Font("宋体", Font.PLAIN, 25);
/**
* bottom字体
*/
private static final Font BOTTOM_FONT = new Font("宋体", Font.ITALIC, 24);
private static final String FORMAT_NAME = "PNG";
/**
* @param title 标题
* @param desc 描述
* @param content 二维码内容
* @param bottom 底部文字
* @param imgLogo 二维码中间图片地址
* @param backgroundImage 背景图片地址
* @param imageY 二维码在背景图片的Y轴位置, X轴是居中的
* @return 返回BufferedImage方便后续处理是生成图片还是生成base64字符串
*/
public static BufferedImage createQrCode(String title, String desc, String content, String bottom, String imgLogo, String backgroundImage, int imageY) throws IOException {
// 创建主模板图片
int maxWidth = getMaxWidth(title, desc, bottom);
int maxHeight = getMaxHeight();
BufferedImage image = new BufferedImage(maxWidth, maxHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
if (StrUtil.isNotBlank(backgroundImage)) {
// 设置图片的背景色为透明, 需要合成到背景图片就设置为透明
image = graphics.getDeviceConfiguration().createCompatibleImage(maxWidth, maxHeight, Transparency.TRANSLUCENT);
} else {
// 设置图片的背景色为白色
graphics.setColor(Color.white);
}
graphics.fillRect(0, 0, maxWidth, maxHeight);
// 动态高度
int height = 0;
// *********************** title ***********************
if (StrUtil.isNotBlank(title)) {
graphics = image.createGraphics();
// 设置字体颜色 black黑 white白
graphics.setColor(Color.black);
// 设置字体
graphics.setFont(TITLE_FONT);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
// 居中 x开始的位置:(图片宽度-字体大小*字的个数)/2
int x = (maxWidth - (TITLE_FONT.getSize() * title.length())) / 2;
int strHeight = getStringHeight(graphics);
graphics.drawString(title, x, strHeight);
height += strHeight;
}
// ********************** desc **********************
if (StrUtil.isNotBlank(desc)) {
graphics = image.createGraphics();
// 设置字体颜色,先设置颜色,再填充内容
graphics.setColor(Color.black);
// 设置字体
graphics.setFont(DESC_FONT);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
// 获取字符高度
int strHeight = getStringHeight(graphics);
// 按行居中显示
String[] info = desc.split("\\$"); // 多行文字以 $ 分隔
for (int i = 0; i < info.length; i++) {
String s = info[i];
// x开始的位置:(图片宽度-字体大小*字的个数)/2
int strWidth = graphics.getFontMetrics().stringWidth(s);
// 总长度减去文字长度的一半 (居中显示)
int startX = (maxWidth - strWidth) / 2;
height += strHeight * (i + 1);
graphics.drawString(s, startX, height);
}
}
// ********************** 插入二维码图片 **********************
Graphics codePic = image.getGraphics();
BufferedImage codeImg;
QrConfig config = new QrConfig();
config.setMargin(2);
config.setWidth(QR_CODE_WIDTH);
config.setHeight(QR_CODE_HEIGHT);
if (StrUtil.isNotBlank(imgLogo)) {
config.setImg(imgLogo);
}
codeImg = QrCodeUtil.generate(content, config);
// 绘制二维码
codePic.drawImage(codeImg, (maxWidth - QR_CODE_WIDTH) / 2, height, QR_CODE_WIDTH, QR_CODE_HEIGHT, null);
codePic.dispose();
// ********************** bottom **********************
if (StrUtil.isNotBlank(bottom)) {
graphics = image.createGraphics();
// 设置字体颜色
graphics.setColor(Color.black);
// 设置字体
graphics.setFont(BOTTOM_FONT);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
// x开始的位置:(图片宽度-字体大小*字的个数)/2
int startX = (maxWidth - (BOTTOM_FONT.getSize() * bottom.length())) / 2;
// 获取字符高度
int footerStrHeight = getStringHeight(graphics);
height += QR_CODE_HEIGHT + footerStrHeight;
graphics.drawString(bottom, startX, height);
}
if (StrUtil.isNotBlank(backgroundImage)) {
// 加载背景模板
ClassPathResource resource = new ClassPathResource(backgroundImage);
InputStream inputStream = resource.getInputStream();
BufferedImage templateImage = ImageIO.read(inputStream);
Graphics graphicsTemplate = templateImage.getGraphics();
// 根据模板宽度 - 二维码宽度 进行居中算出X坐标
int widthX = (templateImage.getWidth() - image.getWidth()) / 2;
// 添加二维码
graphicsTemplate.drawImage(image, widthX, imageY, null);
graphicsTemplate.dispose();
return templateImage;
} else {
return image;
}
}
// 生成图片文件
public static void createImage(BufferedImage image, String fileLocation) {
if (image != null) {
try {
ImageIO.write(image, "png", new File(fileLocation));
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 获取图片base64数据
public static String base64ImageString(BufferedImage image) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();//io流
ImageIO.write(image, FORMAT_NAME, bos);//写入流中
byte[] bytes = bos.toByteArray();//转换成字节
BASE64Encoder encoder = new BASE64Encoder();
String jpgBase64 = encoder.encodeBuffer(bytes).trim();//转换成base64串
jpgBase64 = jpgBase64.replaceAll("\n", "").replaceAll("\r", "");//删除 \r\n
return "data:image/jpg;base64," + jpgBase64;
}
// 字符串总宽度
private static int getStringLength(Graphics g, String str) {
char[] chars = str.toCharArray();
return g.getFontMetrics().charsWidth(chars, 0, str.length());
}
// 字符高度
private static int getStringHeight(Graphics g) {
return g.getFontMetrics().getHeight();
}
// 每一行字符的个数
private static int getRowStrNum(int strNum, int rowWidth, int strWidth) {
return (rowWidth * strNum) / strWidth;
}
// 字符行数
private static int getRows(int strWidth, int rowWidth) {
int rows;
if (strWidth % rowWidth > 0) {
rows = strWidth / rowWidth + 1;
} else {
rows = strWidth / rowWidth;
}
return rows;
}
// 计算二维码图片最大宽度
private static int getMaxWidth(String title, String desc, String bottom) {
int width = 0;
BufferedImage image = new BufferedImage(QR_CODE_WIDTH, QR_CODE_HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
if (StrUtil.isNotBlank(title)) {
width = Math.max(getStringLength(graphics, title), width);
}
if (StrUtil.isNotBlank(desc)) {
width = Math.max(getStringLength(graphics, desc), width);
}
if (StrUtil.isNotBlank(bottom)) {
width = Math.max(getStringLength(graphics, bottom), width);
}
return Math.max(QR_CODE_WIDTH, width);
}
// 计算二维码图片最大高度
private static int getMaxHeight() {
int height = QR_CODE_HEIGHT;
BufferedImage image = new BufferedImage(QR_CODE_WIDTH, QR_CODE_HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
graphics.setFont(TITLE_FONT);
height += getStringHeight(graphics);
graphics.setFont(DESC_FONT);
height += getStringHeight(graphics);
graphics.setFont(BOTTOM_FONT);
height += getStringHeight(graphics);
return height;
}
public static void main(String[] args) throws IOException {
String title = "码牌";
String desc = "知名互联网公司CEO";
String content = "https://www.baidu.com";
String logo = "templates/logo@2x.png";
String bottom = "国家认证";
String backgroundImage = "templates/announcer.png";
// BufferedImage bufferedImage = createQrCode(null, null, content, logo, null, backgroundImage, 290);
BufferedImage bufferedImage = createQrCode(title, desc, content, logo, bottom, backgroundImage, 290);
createImage(bufferedImage, "D:/test.png");
}
}
附上下载的方法
@RestController
public class Controller {
@PostMapping("/downloadQrCode")
public void downloadQrCode(@RequestParam String content, @RequestParam(required = false) String title,
@RequestParam(required = false) String desc, @RequestParam(required = false) String bottom,
HttpServletResponse response) throws IOException {
BufferedImage targetBufferedImage = QrCodeCard.createQrCode(title, desc, content,
bottom, "templates/logo@2x.png", "templates/announcer.png", 290);
OutputStream os = response.getOutputStream();
response.setCharacterEncoding(Charsets.UTF_8.name());
response.setContentType("multipart/form-data");
response.setHeader("content-type", "image/png");
ImageIO.write(targetBufferedImage, "png", os);
}
}