前言
Java后端生成二维码 底部 侧面带有标题,可调节字号
参考文章
使用Java生成二维码图片(亲测)
Reborn_YY使用Java生成二维码图片
图标素材库
Java后台生成图片,前台实现图片下载
jar
保持和spring同时期版本即可
<!-- https://mvnrepository.com/artifact/com.google.zxing/core -->
<!-- 二维码生成工具-->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.3.3</version>
</dependency>
工具类缓冲图像
import com.google.zxing.LuminanceSource;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
public class BufferedImageLuminanceSource extends LuminanceSource {
private final BufferedImage image;
private final int left;
private final int top;
public BufferedImageLuminanceSource(BufferedImage image) {
this(image, 0, 0, image.getWidth(), image.getHeight());
}
public BufferedImageLuminanceSource(BufferedImage image, int left, int top, int width, int height) {
super(width, height);
int sourceWidth = image.getWidth();
int sourceHeight = image.getHeight();
if (left + width > sourceWidth || top + height > sourceHeight) {
throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
}
for (int y = top; y < top + height; y++) {
for (int x = left; x < left + width; x++) {
if ((image.getRGB(x, y) & 0xFF000000) == 0) {
image.setRGB(x, y, 0xFFFFFFFF); // = white
}
}
}
this.image = new BufferedImage(sourceWidth, sourceHeight, BufferedImage.TYPE_BYTE_GRAY);
this.image.getGraphics().drawImage(image, 0, 0, null);
this.left = left;
this.top = top;
}
@Override
public byte[] getRow(int y, byte[] row) {
if (y < 0 || y >= getHeight()) {
throw new IllegalArgumentException("Requested row is outside the image: " + y);
}
int width = getWidth();
if (row == null || row.length < width) {
row = new byte[width];
}
image.getRaster().getDataElements(left, top + y, width, 1, row);
return row;
}
@Override
public byte[] getMatrix() {
int width = getWidth();
int height = getHeight();
int area = width * height;
byte[] matrix = new byte[area];
image.getRaster().getDataElements(left, top, width, height, matrix);
return matrix;
}
@Override
public boolean isCropSupported() {
return true;
}
@Override
public LuminanceSource crop(int left, int top, int width, int height) {
return new BufferedImageLuminanceSource(image, this.left + left, this.top + top, width, height);
}
@Override
public boolean isRotateSupported() {
return true;
}
@Override
public LuminanceSource rotateCounterClockwise() {
int sourceWidth = image.getWidth();
int sourceHeight = image.getHeight();
AffineTransform transform = new AffineTransform(0.0, -1.0, 1.0, 0.0, 0.0, sourceWidth);
BufferedImage rotatedImage = new BufferedImage(sourceHeight, sourceWidth, BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g = rotatedImage.createGraphics();
g.drawImage(image, transform, null);
g.dispose();
int width = getWidth();
return new BufferedImageLuminanceSource(rotatedImage, top, sourceWidth - (left + width), getHeight(), width);
}
}
工具类设置
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;
import sun.font.FontDesignMetrics;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Hashtable;
import java.util.UUID;
/**
* 二维码生成类
*
* @author 18316
*
*/
public class QRCodeUtils {
private static final String CHARSET = "utf-8";
private static final String FORMAT_NAME = "jpg";
// 二维码尺寸
private static final int QRCODE_SIZE = 300;
// LOGO宽度
private static final int WIDTH = 60;
// LOGO高度
private static final int HEIGHT = 60;
// 字体大小
private static final int FONT_SIZE = 18;
/**
* 生成包含 标题说明 LOGO 的二维码图片
*
* @param title 标题 底部显示
* @param content 二维码内容
* @param imgPath LOGO文件路径
* @param needCompress LOGO 是否压缩
* @return 二维码图片流
* @throws Exception
*/
private static BufferedImage createImage(String title, String content, String imgPath, boolean needCompress) throws Exception {
Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
hints.put(EncodeHintType.MARGIN, 1);
BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE,
hints);
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
int tempHeight = height;
// 二维码图片大小 有文字放大
boolean notEmptyTitle = StringUtils.isNotEmpty(title);
if (notEmptyTitle) {
tempHeight += 20;
}
BufferedImage image = new BufferedImage(width, tempHeight, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
}
}
// 插入图片LOGO
if (StringUtils.isNotEmpty(imgPath)) {
QRCodeUtils.insertImage(image, imgPath, needCompress);
}
//插入文字
if (notEmptyTitle) {
addFontImage(image, "【" + title + "】");
}
return image;
}
/**
* 添加 底部图片文字
*
* @param source 图片源
* @param declareText 文字本文
*/
private static void addFontImage(BufferedImage source, String declareText) {
//设置文本图片宽高
BufferedImage textImage = strToImage(declareText, QRCODE_SIZE, 40);
Graphics2D graph = source.createGraphics();
//开启文字抗锯齿
graph.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
int width = textImage.getWidth(null);
int height = textImage.getHeight(null);
Image src = textImage;
//画图 文字图片最终显示位置 在Y轴偏移量 从上往下算
graph.drawImage(src, 0, QRCODE_SIZE - 20, width, height, null);
graph.dispose();
}
/**
* 处理文字大小 生成文字图片
*
* @param str
* @param width
* @param height
* @return 文本图片
*/
private static BufferedImage strToImage(String str, int width, int height) {
BufferedImage textImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) textImage.getGraphics();
//开启文字抗锯齿
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.setBackground(Color.WHITE);
g2.clearRect(0, 0, width, height);
g2.setPaint(Color.BLACK);
FontRenderContext context = g2.getFontRenderContext();
Font font = new Font("微软雅黑", Font.BOLD, FONT_SIZE);
g2.setFont(font);
LineMetrics lineMetrics = font.getLineMetrics(str, context);
FontMetrics fontMetrics = FontDesignMetrics.getMetrics(font);
float offset = (width - fontMetrics.stringWidth(str)) / 2;
float y = (height + lineMetrics.getAscent() - lineMetrics.getDescent() - lineMetrics.getLeading()) / 2;
g2.drawString(str, (int) offset, (int) y);
return textImage;
}
/**
* 插入LOGO
*
* @param source 二维码图片
* @param imgPath LOGO图片地址
* @param needCompress 是否压缩
* @throws Exception
*/
private static void insertImage(BufferedImage source, String imgPath, boolean needCompress) throws Exception {
File file = new File(imgPath);
if (!file.exists()) {
System.err.println("" + imgPath + " 该文件不存在!");
return;
}
Image src = ImageIO.read(new File(imgPath));
int width = src.getWidth(null);
int height = src.getHeight(null);
if (needCompress) { // 压缩LOGO
if (width > WIDTH) {
width = WIDTH;
}
if (height > HEIGHT) {
height = HEIGHT;
}
Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = tag.getGraphics();
g.drawImage(image, 0, 0, null); // 绘制缩小后的图
g.dispose();
src = image;
}
// 插入LOGO
Graphics2D graph = source.createGraphics();
int x = (QRCODE_SIZE - width) / 2;
int y = (QRCODE_SIZE - height) / 2;
graph.drawImage(src, x, y, width, height, null);
Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
graph.setStroke(new BasicStroke(3f));
graph.draw(shape);
graph.dispose();
}
/**
* 生成二维码(内嵌LOGO) 本地保存
*
* @param content 内容
* @param imgPath LOGO地址
* @param destPath 存放目录
* @param needCompress 是否压缩LOGO
* @throws Exception
*/
public static String encode(String content, String imgPath, String destPath, boolean needCompress)
throws Exception {
BufferedImage image = QRCodeUtils.createImage(null,content, imgPath, needCompress);
mkdirs(destPath);
// 随机生成二维码图片文件名
String file = UUID.randomUUID() + ".jpg";
ImageIO.write(image, FORMAT_NAME, new File(destPath + "/" + file));
return destPath + file;
}
/**
* 创建二维码 底部有标题 LOGO
*
* @param title 标题
* @param content 二维码内容
* @param imgPath 图片地址
* @param needCompress LOGO是否压缩 推荐为True
* @param response 响应流
*/
public static void createTitleImage(String title, String content, String imgPath, boolean needCompress, HttpServletResponse response) {
BufferedImage image = null;
ImageOutputStream imageOutput = null;
long length = 0;
try {
//创建二维码
image = QRCodeUtils.createImage(title, content, imgPath, needCompress);
//步骤一:BufferedImage 转 InputStream
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
imageOutput = ImageIO.createImageOutputStream(byteArrayOutputStream);
ImageIO.write(image, "png", imageOutput);
//步骤二:获得文件长度
length = imageOutput.length();
// 文件名 类型需要注明
String fileName = "6S-【" + title + "】点检.png";
//设置文件长度
response.setContentLength((int) length);
//输入流
InputStream inputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
//步骤三:传入文件名、输入流、响应
fileDownload(fileName, inputStream, response);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//文件下载方法,工具类
public static void fileDownload(String filename, InputStream input, HttpServletResponse response) {
try {
byte[] buffer = new byte[input.available()];
input.read(buffer);
input.close();
// 清空response
response.reset();
// 设置response的Header
response.addHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes("utf-8"), "ISO-8859-1"));
OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/octet-stream");
toClient.write(buffer);
toClient.flush();
//关闭,即下载
toClient.close();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
*
* @author lanyuan Email: mmm333zzz520@163.com
* @date 2013-12-11 上午10:16:36
* @param destPath 存放目录
*/
public static void mkdirs(String destPath) {
File file = new File(destPath);
// 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
if (!file.exists() && !file.isDirectory()) {
file.mkdirs();
}
}
}
调用
有无LOGO调用
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class Test {
//二维码内容
private static String content = "https://www.baidu.com";
// 二维码保存的路径
private static String path = "D:/Test/";
//LOGO本地路径
private static String logopath = "D:/Test/6s1.png";
public static void main(String[] args) {
//创建有LOGO的二维码
System.out.println("开始生成...");
QRCode();
QRCodeLogo();
System.out.println("生成完毕!");
//创建有标题的二维码
QRCodeUtils.createTitleImage("压机上线工序","https://www.baidu.com","",true,response);
}
/**
* 生成带有LOGO的二维码
*/
public static void QRCodeLogo() {
try {
System.out.println(QRCodeUtils.encode(content,logopath,path,true));
} catch (WriterException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}catch (Exception e){
}
}
/**
* 生成纯二维码
*/
public static void QRCode() {
try {
String codeName = UUID.randomUUID().toString();// 二维码的图片名
String imageType = "jpg";// 图片类型
MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
Map<EncodeHintType, String> hints = new HashMap<EncodeHintType, String>();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, 400, 400, hints);
File file1 = new File(path, codeName + "." + imageType);
MatrixToImageWriter.writeToFile(bitMatrix, imageType, file1);
} catch (WriterException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}catch (Exception e){
}
}
}
竖版字体(可调字号)
java 字体大小 像素_字体的大小(pt)和像素(px)如何转换?
/**
* 生成包含 标题说明 LOGO 的二维码图片
*
* @param title 标题 底部显示
* @param content 二维码内容
* @param imgPath LOGO文件路径
* @param needCompress LOGO 是否压缩
* @return 二维码图片流
* @throws Exception
*/
private static BufferedImage createImage(String title, String content, String imgPath, boolean needCompress) throws Exception {
Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
hints.put(EncodeHintType.MARGIN, 1);
BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, hints);
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
int tempWidth = width;
// 二维码图片大小 有文字按照所在位置加长加宽
boolean notEmptyTitle = StringUtils.isNotEmpty(title);
if (notEmptyTitle) {
tempWidth+=50;
}
//制作白板 填充二维码
BufferedImage image = new BufferedImage(tempWidth, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
}
}
// 插入图片LOGO
if (StringUtils.isNotEmpty(imgPath)) {
QRCodeUtils.insertImage(image, imgPath, needCompress);
}
//插入文字
if (notEmptyTitle) {
addFontImage(image, "︻" + title + "︼");
}
return image;
}
/**
* 添加 底部图片文字
*
* @param source 图片源
* @param declareText 文字本文
*/
private static void addFontImage(BufferedImage source, String declareText) {
//设置文本图片宽高
BufferedImage textImage = strToImage(declareText, 50, QRCODE_SIZE);
Graphics2D graph = source.createGraphics();
//开启文字抗锯齿
graph.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
int width = textImage.getWidth(null);
int height = textImage.getHeight(null);
Image src = textImage;
//画图 文字图片最终显示位置 在Y轴偏移量 从上往下算
graph.drawImage(src, QRCODE_SIZE, 0, width, height, null);
graph.dispose();
}
/**
* 处理文字大小 生成文字图片
*
* @param str
* @param width
* @param height
* @return 文本图片
*/
private static BufferedImage strToImage(String str, int width, int height) {
BufferedImage textImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) textImage.getGraphics();
//开启文字抗锯齿
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.setBackground(Color.GREEN);
g2.clearRect(0, 0, width, height);
g2.setPaint(Color.BLACK);
int length = str.length();
//字体大小
int fontSize=0;
//字体间距 最多支持32个字 超长可追加设置
int fontSpacing=0;
if(length<8) {
fontSize=FONT_SIZE;
fontSpacing=5;
}else if(8<length&&length<16){
fontSize=12;
fontSpacing=3;
}else if(16<length&&length<32){
fontSize=7;
fontSpacing=1;
}
//加入字体
Font font = new Font("微软雅黑", Font.BOLD, fontSize);
g2.setFont(font);
//字体像素大小 1/2.8
float fontPx= (float)(fontSize/72.0)*96;
//计算第一个文字书写Y轴位置 从上往下
float y=height/2-((length/2) *fontPx);
//X轴补偿系数 留白 保证对齐
float offset=(width-fontPx)/2;
//截取单个字符 循环排列每个字竖坐标
String[] split = str.split("");
for (String fontStr : split) {
g2.drawString(fontStr, (int) offset, (int) y);
//下一个字体位置
y+=(fontPx+fontSpacing);
}
return textImage;
}
下载
及时性生成推荐IO流,没必要本地存储
/**
* 测试下载
* @param response
*/
@ApiOperation("下载二维码")
@GetMapping("/downloadQRImag")
public void downloadQRImag(HttpServletResponse response){
try {
QRCodeUtils.downloadQRImag("https://www.baidu.com","D:/Test/6s3.png",response,true);
}catch (Exception e){
logger.error(e.getMessage());
}
}
注意
二维码的复杂程度决定了是否能填充完全