当前位置:网站首页>背景图和二维码合成
背景图和二维码合成
2022-07-24 22:12:00 【努力奋斗GO】
需求: 类似于做一个码牌, 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/[email protected]";
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/[email protected]", "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);
}
}边栏推荐
- Available parameters of ansible Playbook
- Enterprise operation we media can't "self Hi": the content should be grounded, not advertising
- The kettle job implementation runs a kettle conversion task every 6S
- Ranking of engineering project management software
- PHP get thumbnails
- Web3 security go + security
- Gradle learning - getting started with gradle
- PCL point cloud processing to find the two endpoints of the line point set (57)
- Website resources
- 关于板载继电器供电不足引起不能吸合的问题
猜你喜欢

Homework of the 20th week

Use kettle to read the data in Excel file and store it in MySQL

单调栈结构练习——子数组最小值的累加和

AC自动机

Gradle learning - getting started with gradle

IndexTree2D

Kubernetes scheduling concept and workflow

Plane regularization of PCL point cloud processing (55)

About constant modifier const

Violent recursion - detailed explanation of Queen n & how to optimize with bit operation
随机推荐
CA证书制作实战
单调栈结构练习——子数组最小值的累加和
The tragic experience of installing scikitlearn on win764
QT learning vs creating QT items shows instances where object references are not set to objects
Flex layout
Implement redis sentinel to simulate master failure scenarios
[cloud native] Devops (IV): integrated sonar Qube
IP first experiment hdcl encapsulates PPP, chap, mGRE
IndexTree
Violent recursion - detailed explanation of Queen n & how to optimize with bit operation
ODBC executes stored procedure to get return value
About the word 'don't remember'
PCL点云处理之直线点集投影规则化(五十六)
Process / thread synchronization mechanism
[which is better to use, apopost or apifox? Just read this!]
IndexTree
How to adjust the default output of vscode to the debugging console to the terminal and the problem of garbled code in both
工业物联网中的时序数据
Cell special issue | application and future prediction of AI in protein structure, precision medicine, antibody therapy [review]
IndexTree2D