当前位置:网站首页>背景图和二维码合成
背景图和二维码合成
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);
}
}边栏推荐
- Implementation of graph structure, from point to edge and then to graph
- Homework of the 20th week
- Li Kou 1184. Distance between bus stops
- DDoS attack classification
- Push information to wechat through enterprise wechat self built application
- [cloud native] Devops (IV): integrated sonar Qube
- Archsummit: evolution of the underlying framework of cherished microservices
- VC prompts to recompile every time you press F5 to run
- PCL点云处理之找到直线点集的两个端点(五十七)
- Database - Metadata databasemetadata beginner
猜你喜欢

From Fibonacci sequence to matrix fast power technique

"Yuan universe 2086" outsold "San ti" in one-day sales and won the champion of JD books' one-day science fiction list

Get the solution to the slow running speed of Mengxin Xiaobai computer! ٩ ( ‘ ω‘ )و get! ٩ ( ‘ ω‘ )و

Li Kou 1184. Distance between bus stops

Go+ language

Archsummit: evolution of the underlying framework of cherished microservices
![[Apipost和Apifox哪个更好用?看这篇就够了!]](/img/58/4dfe5c56d12e8e88b0a7f3583ff0a4.jpg)
[Apipost和Apifox哪个更好用?看这篇就够了!]

一文读懂Elephant Swap的LaaS方案的优势之处
![[which is better to use, apopost or apifox? Just read this!]](/img/58/4dfe5c56d12e8e88b0a7f3583ff0a4.jpg)
[which is better to use, apopost or apifox? Just read this!]

IndexTree2D
随机推荐
单调栈结构练习——子数组最小值的累加和
百度网盘+Chrom插件
PCL点云处理之平面规则化(五十五)
From Fibonacci sequence to matrix fast power technique
Dialogue with celebrities: where are the opportunities and challenges in the second half when brands gather at the shuzang track?
How about Minsheng futures? Is it safe?
Projection regularization of line point set in PCL point cloud processing (56)
Violent recursion - detailed explanation of Queen n & how to optimize with bit operation
Using FRP to achieve intranet penetration
When texturebrush is created, it prompts that there is insufficient memory
Integrated swagger learning
【数据库学习】Redis 解析器&&单线程&&模型
单调栈结构
PCL point cloud processing ply file reading and saving (54)
Filter list
PCL点云处理之均匀采样抽稀(六十一)
"Fundamentals of program design" Chapter 10 function and program structure 7-3 recursive realization of reverse order output integer (15 points)
PCL点云处理之CSF布料模拟滤波(五十九)
Li Kou 1184. Distance between bus stops
QT learning vs creating QT items shows instances where object references are not set to objects