package com.property.common.utils.qrcode;
import com.google.common.collect.Maps;
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 com.property.common.utils.DESUtil;
import com.property.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.util.Map;
/**
* @author Administrator
*/
@Slf4j
public class QrCodeUtils {
/**
* 字体大小
*/
private static final int FONT_STYLE = 1;
/**
* 图片的宽度
*/
private static final int IMAGE_WIDTH = 350;
/**
* 图片的高度(需按实际内容高度进行调整)
*/
private static final int IMAGE_HEIGHT = 350;
/**
* 二维码的宽度
*/
private static final int QR_CODE_WIDTH = 220;
/**
* 二维码的宽度
*/
private static final int QR_CODE_HEIGHT = 220;
/**
* 字体大小
*/
private static final int FONT_SIZE = 15;
/**
* 生成二维码的格式
*/
private static final String FORMAT = "jpg";
/**
* 根据内容生成二维码数据
*
* @param content 二维码文字内容[为了信息安全性,一般都要先进行数据加密]
* @param imgWidth 二维码图片宽度
* @param imgLength 二维码图片高度
*/
private static BitMatrix createQrcodeMatrix(String content, int imgWidth, int imgLength) {
Map<EncodeHintType, Object> hints = Maps.newEnumMap(EncodeHintType.class);
// 设置字符编码
hints.put(EncodeHintType.CHARACTER_SET, Charsets.UTF_8.name());
// 指定纠错等级
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
//设置二维码四周白色区域的大小
hints.put(EncodeHintType.MARGIN, 1);
try {
String encrypt = DESUtil.encrypt(content);
return new MultiFormatWriter().encode(StringUtils.isNotBlank(encrypt) ? encrypt : "很抱歉,二维码生成失败!", BarcodeFormat.QR_CODE, imgWidth == 0 ? IMAGE_WIDTH : imgWidth, imgLength == 0 ? IMAGE_HEIGHT : imgLength, hints);
} catch (Exception e) {
return null;
}
}
/**
* 根据指定边长创建生成的二维码,允许配置logo属性
*
* @param content 二维码内容
* @param logoFile logo 文件对象,可以为空
* @param logoConfig logo配置,可设置logo展示长宽,边框颜色
* @param imgWidth 二维码宽度
* @param imgLength 二维码长度
* @return 二维码图片的字节数组
*/
public static byte[] createQrcode(String content, File logoFile, LogoConfig logoConfig, int imgWidth, int imgLength) {
if (logoFile != null && !logoFile.exists()) {
throw new IllegalArgumentException("请提供正确的logo文件!");
}
BitMatrix qrCodeMatrix = createQrcodeMatrix(content, imgWidth, imgLength);
if (qrCodeMatrix == null) {
return null;
}
try {
File file = Files.createTempFile("qrcode_log", "." + FORMAT).toFile();
MatrixToImageWriter.writeToFile(qrCodeMatrix, FORMAT, file);
if (logoFile != null) {
// 添加logo图片, 此处一定需要重新进行读取,而不能直接使用二维码的BufferedImage 对象
BufferedImage img = ImageIO.read(file);
overlapImage(img, FORMAT, file.getAbsolutePath(), logoFile, logoConfig);
}
return toByteArray(file);
} catch (Exception e) {
return null;
}
}
/**
* 根据指定边长创建生成的二维码,
*
* @param content 二维码内容
* @param imgWidth 二维码宽度
* @param imgLength 二维码长度
* @return BufferedImage
*/
public static BufferedImage bufferedImage(String content, int imgWidth, int imgLength) {
BitMatrix qrCodeMatrix = createQrcodeMatrix(content, imgWidth, imgLength);
if (qrCodeMatrix == null) {
return null;
}
try {
return MatrixToImageWriter.toBufferedImage(qrCodeMatrix);
} catch (Exception e) {
return null;
}
}
/**
* 把生成的图片写到本地磁盘
*
* @param qrCodeContent 二维码内容
* @param headText 头部文字内容
* @param pressText 增加的文字
* @throws Exception
*/
public static InputStream generateQrCode(String qrCodeContent, String headText, String pressText) {
// 头部文字区域高度
int titleHeight = 50;
// 创建主模板图片
BufferedImage image = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D main = image.createGraphics();
// 设置图片的背景色
//白色
main.setColor(Color.white);
main.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
// BufferedImage image = bufferedImage(qrCodeContent, IMAGE_WIDTH, IMAGE_HEIGHT);
ByteArrayOutputStream byteArrayOutputStream = null;
try {
// String path = Thread.currentThread().getContextClassLoader().getResource("static/simsun.ttc").getPath();
// 动态高度
int height = 0;
//***********************页面头部 文字****************
Graphics2D titleRight = image.createGraphics();
// 设置字体颜色 black黑 white白
titleRight.setColor(Color.black);
// 设置字体
Font titleFont = new Font("NSimSun", Font.BOLD, 25);
// Font titleFont = SystemLoadFont.styleFont(path, Font.BOLD, 25);
titleRight.setFont(titleFont);
titleRight.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
// 居中 x开始的位置:(图片宽度-字体大小*字的个数)/2
int x = (IMAGE_WIDTH - (titleFont.getSize() * headText.length())) / 2;
titleRight.drawString(headText, x, (titleHeight) / 2 + 10);
height += titleHeight;
//**********************中间文字部分*********
Graphics2D centerWord = image.createGraphics();
// 设置字体颜色,先设置颜色,再填充内容
centerWord.setColor(Color.black);
// 设置字体
// Font wordFont = SystemLoadFont.styleFont(path, Font.PLAIN, 15);
Font wordFont = new Font("NSimSun", Font.PLAIN, 15);
centerWord.setFont(wordFont);
centerWord.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
String[] info = pressText.split("&");
for (String s : info) {
// x开始的位置:(图片宽度-字体大小*字的个数)/2
int strWidth = centerWord.getFontMetrics().stringWidth(s);
// 总长度减去文字长度的一半 (居中显示)
int startX = (IMAGE_WIDTH - strWidth) / 2;
height += 20;
centerWord.drawString(s, startX, height);
}
//***************插入二维码图片***********************************************
Graphics codePic = image.getGraphics();
BufferedImage codeImg;
codeImg = bufferedImage(qrCodeContent, QR_CODE_WIDTH, QR_CODE_HEIGHT);
// 绘制二维码
codePic.drawImage(codeImg, (IMAGE_WIDTH - QR_CODE_WIDTH) / 2, (height + 5), QR_CODE_WIDTH, QR_CODE_HEIGHT, null);
codePic.dispose();
//**********************底部公司名字*********
Graphics2D g2 = (Graphics2D) image.getGraphics();
//开启文字抗锯齿
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
Font font = new Font("NSimSun", Font.PLAIN, 15);
// Font font = SystemLoadFont.styleFont(path, Font.PLAIN, 15);
g2.setFont(font);
pressText = "公司名字";
//x开始的位置:(图片宽度-字体大小*字的个数)/2
int startX = (IMAGE_WIDTH - (font.getSize() * pressText.length())) / 2;
// //y开始的位置:图片高度-(图片高度-图片宽度)/2
// height += QR_CODE_HEIGHT;
// 获取字符高度
int footerStrHeight = getStringHeight(g2);
height += QR_CODE_HEIGHT + footerStrHeight;
// 设置字体颜色
g2.setColor(Color.black);
g2.drawString(pressText, startX, height);
g2.dispose();
image.flush();
byteArrayOutputStream = new ByteArrayOutputStream();
ImageOutputStream imageOutput = ImageIO.createImageOutputStream(byteArrayOutputStream);
ImageIO.write(image, "jpg", imageOutput);
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
// 图片文件输入输出流必须记得关闭
IOUtils.closeQuietly(byteArrayOutputStream);
}
}
// 字符高度
private static int getStringHeight(Graphics g) {
return g.getFontMetrics().getHeight();
}
// 字符串总宽度
private static int getStringLength(Graphics g, String str) {
char[] chars = str.toCharArray();
return g.getFontMetrics().charsWidth(chars, 0, str.length());
}
/**
* 将文件转换为字节数组,
* 使用MappedByteBuffer,可以在处理大文件时,提升性能
*
* @param file 文件
* @return 二维码图片的字节数组
*/
private static byte[] toByteArray(File file) {
try (FileChannel fc = new RandomAccessFile(file, "r").getChannel();) {
MappedByteBuffer byteBuffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load();
byte[] result = new byte[(int) fc.size()];
if (byteBuffer.remaining() > 0) {
byteBuffer.get(result, 0, byteBuffer.remaining());
}
return result;
} catch (Exception e) {
return null;
}
}
/**
* 将logo添加到二维码中间
*
* @param image 生成的二维码图片对象
* @param imagePath 图片保存路径
* @param logoFile logo文件对象
* @param format 图片格式
*/
private static void overlapImage(BufferedImage image, String format, String imagePath, File logoFile,
LogoConfig logoConfig) throws IOException {
try {
BufferedImage logo = ImageIO.read(logoFile);
Graphics2D g = image.createGraphics();
// 考虑到logo图片贴到二维码中,建议大小不要超过二维码的1/5;
int width = image.getWidth() / logoConfig.getLogoPart();
int height = image.getHeight() / logoConfig.getLogoPart();
// logo起始位置,此目的是为logo居中显示
int x = (image.getWidth() - width) / 2;
int y = (image.getHeight() - height) / 2;
// 绘制图
g.drawImage(logo, x, y, width, height, null);
// 给logo画边框
// 构造一个具有指定线条宽度以及 cap 和 join 风格的默认值的实心 BasicStroke
g.setStroke(new BasicStroke(logoConfig.getBorder()));
g.setColor(logoConfig.getBorderColor());
g.drawRect(x, y, width, height);
g.dispose();
// 写入logo图片到二维码
ImageIO.write(image, format, new File(imagePath));
} catch (Exception e) {
throw new IOException("二维码添加logo时发生异常!", e);
}
}
}
生成的样图:
用到的字体是 新宋体
package com.property.common.utils.qrcode;
import com.google.zxing.common.BitMatrix;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
/**
* @Description 二维码生成类
* @Author wangliangzhi
* @Date 日期:2021/11/3 时间:17:41
**/
public final class MatrixToImageWriter {
private static final int BLACK = 0xFF000000;
private static final int WHITE = 0xFFFFFFFF;
private MatrixToImageWriter() {
}
public static BufferedImage toBufferedImage(BitMatrix matrix) {
int width = matrix.getWidth();
int height = matrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
}
}
return image;
}
public static void writeToFile(BitMatrix matrix, String format, File file)
throws IOException {
BufferedImage image = toBufferedImage(matrix);
if (!ImageIO.write(image, format, file)) {
throw new IOException("Could not write an image of format " + format + " to " + file);
}
}
public static void writeToStream(BitMatrix matrix, String format, OutputStream stream)
throws IOException {
BufferedImage image = toBufferedImage(matrix);
if (!ImageIO.write(image, format, stream)) {
throw new IOException("Could not write an image of format " + format);
}
}
}
图片中的文字换行 是根据
String[] info = pressText.split("&");
这行代码实现的