535 lines
17 KiB
Java
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cn.hutool.extra.qrcode;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.img.Img;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Binarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.common.HybridBinarizer;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
/**
* 基于Zxing的二维码工具类支持
* <ul>
* <li>二维码生成和识别,见{@link BarcodeFormat#QR_CODE}</li>
* <li>条形码生成和识别,见{@link BarcodeFormat#CODE_39}等很多标准格式</li>
* </ul>
*
* @author looly
* @since 4.0.2
*/
public class QrCodeUtil {
/**
* 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示
*
* @param content 内容
* @param qrConfig 二维码配置,包括长、宽、边距、颜色等
* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}
* @param logoBase64 logo 图片的 base64 编码
* @return 图片 Base64 编码字符串
*/
public static String generateAsBase64(String content, QrConfig qrConfig, String imageType, String logoBase64) {
return generateAsBase64(content, qrConfig, imageType, Base64.decode(logoBase64));
}
/**
* 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示
*
* @param content 内容
* @param qrConfig 二维码配置,包括长、宽、边距、颜色等
* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}
* @param logo logo 图片的byte[]
* @return 图片 Base64 编码字符串
*/
public static String generateAsBase64(String content, QrConfig qrConfig, String imageType, byte[] logo) {
return generateAsBase64(content, qrConfig, imageType, ImgUtil.toImage(logo));
}
/**
* 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示
*
* @param content 内容
* @param qrConfig 二维码配置,包括长、宽、边距、颜色等
* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}
* @param logo logo 图片的byte[]
* @return 图片 Base64 编码字符串
*/
public static String generateAsBase64(String content, QrConfig qrConfig, String imageType, Image logo) {
qrConfig.setImg(logo);
return generateAsBase64(content, qrConfig, imageType);
}
/**
* 生成 Base64 编码格式的二维码,以 String 形式表示
*
* <p>
* 输出格式为: data:image/[type];base64,[data]
* </p>
*
* @param content 内容
* @param qrConfig 二维码配置,包括长、宽、边距、颜色等
* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}
* @return 图片 Base64 编码字符串
*/
public static String generateAsBase64(String content, QrConfig qrConfig, String imageType) {
final BufferedImage img = generate(content, qrConfig);
return ImgUtil.toBase64DataUri(img, imageType);
}
/**
* @param content 内容
* @param qrConfig 二维码配置,包括长、宽、边距、颜色等
* @return SVG矢量图字符串
* @since 5.8.6
*/
public static String generateAsSvg(String content, QrConfig qrConfig) {
BitMatrix bitMatrix = encode(content, qrConfig);
return toSVG(bitMatrix, qrConfig);
}
/**
* 生成PNG格式的二维码图片以byte[]形式表示
*
* @param content 内容
* @param width 宽度
* @param height 高度
* @return 图片的byte[]
* @since 4.0.10
*/
public static byte[] generatePng(String content, int width, int height) {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
generate(content, width, height, ImgUtil.IMAGE_TYPE_PNG, out);
return out.toByteArray();
}
/**
* 生成PNG格式的二维码图片以byte[]形式表示
*
* @param content 内容
* @param config 二维码配置,包括长、宽、边距、颜色等
* @return 图片的byte[]
* @since 4.1.2
*/
public static byte[] generatePng(String content, QrConfig config) {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
generate(content, config, ImgUtil.IMAGE_TYPE_PNG, out);
return out.toByteArray();
}
/**
* 生成二维码到文件,二维码图片格式取决于文件的扩展名
*
* @param content 文本内容
* @param width 宽度
* @param height 高度
* @param targetFile 目标文件,扩展名决定输出格式
* @return 目标文件
*/
public static File generate(String content, int width, int height, File targetFile) {
final BufferedImage image = generate(content, width, height);
ImgUtil.write(image, targetFile);
return targetFile;
}
/**
* 生成二维码到文件,二维码图片格式取决于文件的扩展名
*
* @param content 文本内容
* @param config 二维码配置,包括长、宽、边距、颜色等
* @param targetFile 目标文件,扩展名决定输出格式
* @return 目标文件
* @since 4.1.2
*/
public static File generate(String content, QrConfig config, File targetFile) {
final BufferedImage image = generate(content, config);
ImgUtil.write(image, targetFile);
return targetFile;
}
/**
* 生成二维码到输出流
*
* @param content 文本内容
* @param width 宽度
* @param height 高度
* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}
* @param out 目标流
*/
public static void generate(String content, int width, int height, String imageType, OutputStream out) {
final BufferedImage image = generate(content, width, height);
ImgUtil.write(image, imageType, out);
}
/**
* 生成二维码到输出流
*
* @param content 文本内容
* @param config 二维码配置,包括长、宽、边距、颜色等
* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}
* @param out 目标流
* @since 4.1.2
*/
public static void generate(String content, QrConfig config, String imageType, OutputStream out) {
final BufferedImage image = generate(content, config);
ImgUtil.write(image, imageType, out);
}
/**
* 生成二维码图片
*
* @param content 文本内容
* @param width 宽度
* @param height 高度
* @return 二维码图片(黑白)
*/
public static BufferedImage generate(String content, int width, int height) {
return generate(content, new QrConfig(width, height));
}
/**
* 生成二维码或条形码图片
*
* @param content 文本内容
* @param format 格式,可选二维码或者条形码
* @param width 宽度
* @param height 高度
* @return 二维码图片(黑白)
*/
public static BufferedImage generate(String content, BarcodeFormat format, int width, int height) {
return generate(content, format, new QrConfig(width, height));
}
/**
* 生成二维码图片
*
* @param content 文本内容
* @param config 二维码配置,包括长、宽、边距、颜色等
* @return 二维码图片(黑白)
* @since 4.1.2
*/
public static BufferedImage generate(String content, QrConfig config) {
return generate(content, BarcodeFormat.QR_CODE, config);
}
/**
* 生成二维码或条形码图片<br>
* 只有二维码时QrConfig中的图片才有效
*
* @param content 文本内容
* @param format 格式,可选二维码、条形码等
* @param config 二维码配置,包括长、宽、边距、颜色等
* @return 二维码图片(黑白)
* @since 4.1.14
*/
public static BufferedImage generate(String content, BarcodeFormat format, QrConfig config) {
final BitMatrix bitMatrix = encode(content, format, config);
final BufferedImage image = toImage(bitMatrix, config.foreColor, config.backColor);
final Image logoImg = config.img;
if (null != logoImg && BarcodeFormat.QR_CODE == format) {
// 只有二维码可以贴图
final int qrWidth = image.getWidth();
final int qrHeight = image.getHeight();
int width;
int height;
// 按照最短的边做比例缩放
if (qrWidth < qrHeight) {
width = qrWidth / config.ratio;
height = logoImg.getHeight(null) * width / logoImg.getWidth(null);
} else {
height = qrHeight / config.ratio;
width = logoImg.getWidth(null) * height / logoImg.getHeight(null);
}
Img.from(image).pressImage(//
Img.from(logoImg).round(0.3).getImg(), // 圆角
new Rectangle(width, height), //
1//
);
}
return image;
}
// ------------------------------------------------------------------------------------------------------------------- encode
/**
* 将文本内容编码为二维码
*
* @param content 文本内容
* @param width 宽度
* @param height 高度
* @return {@link BitMatrix}
*/
public static BitMatrix encode(String content, int width, int height) {
return encode(content, BarcodeFormat.QR_CODE, width, height);
}
/**
* 将文本内容编码为二维码
*
* @param content 文本内容
* @param config 二维码配置,包括长、宽、边距、颜色等
* @return {@link BitMatrix}
* @since 4.1.2
*/
public static BitMatrix encode(String content, QrConfig config) {
return encode(content, BarcodeFormat.QR_CODE, config);
}
/**
* 将文本内容编码为条形码或二维码
*
* @param content 文本内容
* @param format 格式枚举
* @param width 宽度
* @param height 高度
* @return {@link BitMatrix}
*/
public static BitMatrix encode(String content, BarcodeFormat format, int width, int height) {
return encode(content, format, new QrConfig(width, height));
}
/**
* 将文本内容编码为条形码或二维码
*
* @param content 文本内容
* @param format 格式枚举
* @param config 二维码配置,包括长、宽、边距、颜色等
* @return {@link BitMatrix}
* @since 4.1.2
*/
public static BitMatrix encode(String content, BarcodeFormat format, QrConfig config) {
final MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
if (null == config) {
// 默认配置
config = new QrConfig();
}
BitMatrix bitMatrix;
try {
bitMatrix = multiFormatWriter.encode(content, format, config.width, config.height, config.toHints(format));
} catch (WriterException e) {
throw new QrCodeException(e);
}
return bitMatrix;
}
// ------------------------------------------------------------------------------------------------------------------- decode
/**
* 解码二维码或条形码图片为文本
*
* @param qrCodeInputstream 二维码输入流
* @return 解码文本
*/
public static String decode(InputStream qrCodeInputstream) {
return decode(ImgUtil.read(qrCodeInputstream));
}
/**
* 解码二维码或条形码图片为文本
*
* @param qrCodeFile 二维码文件
* @return 解码文本
*/
public static String decode(File qrCodeFile) {
return decode(ImgUtil.read(qrCodeFile));
}
/**
* 将二维码或条形码图片解码为文本
*
* @param image {@link Image} 二维码图片
* @return 解码后的文本
*/
public static String decode(Image image) {
return decode(image, true, false);
}
/**
* 将二维码或条形码图片解码为文本<br>
* 此方法会尝试使用{@link HybridBinarizer}和{@link GlobalHistogramBinarizer}两种模式解析<br>
* 需要注意部分二维码如果不带logo使用PureBarcode模式会解析失败此时须设置此选项为false。
*
* @param image {@link Image} 二维码图片
* @param isTryHarder 是否优化精度
* @param isPureBarcode 是否使用复杂模式扫描带logo的二维码设为true
* @return 解码后的文本
* @since 4.3.1
*/
public static String decode(Image image, boolean isTryHarder, boolean isPureBarcode) {
return decode(image, buildHints(isTryHarder, isPureBarcode));
}
/**
* 将二维码或条形码图片解码为文本<br>
* 此方法会尝试使用{@link HybridBinarizer}和{@link GlobalHistogramBinarizer}两种模式解析<br>
* 需要注意部分二维码如果不带logo使用PureBarcode模式会解析失败此时须设置此选项为false。
*
* @param image {@link Image} 二维码图片
* @param hints 自定义扫码配置,包括算法、编码、复杂模式等
* @return 解码后的文本
* @since 5.7.12
*/
public static String decode(Image image, Map<DecodeHintType, Object> hints) {
final MultiFormatReader formatReader = new MultiFormatReader();
formatReader.setHints(hints);
final LuminanceSource source = new BufferedImageLuminanceSource(ImgUtil.toBufferedImage(image));
Result result = _decode(formatReader, new HybridBinarizer(source));
if (null == result) {
result = _decode(formatReader, new GlobalHistogramBinarizer(source));
}
return null != result ? result.getText() : null;
}
/**
* BitMatrix转BufferedImage
*
* @param matrix BitMatrix
* @param foreColor 前景色
* @param backColor 背景色(null表示透明背景)
* @return BufferedImage
* @since 4.1.2
*/
public static BufferedImage toImage(BitMatrix matrix, int foreColor, Integer backColor) {
final int width = matrix.getWidth();
final int height = matrix.getHeight();
BufferedImage image = new BufferedImage(width, height, null == backColor ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
if (matrix.get(x, y)) {
image.setRGB(x, y, foreColor);
} else if (null != backColor) {
image.setRGB(x, y, backColor);
}
}
}
return image;
}
/**
* @param matrix BitMatrix
* @param qrConfig 二维码配置,包括长、宽、边距、颜色等
* @return SVG矢量图字符串
* @since 5.8.6
*/
public static String toSVG(BitMatrix matrix,QrConfig qrConfig) {
return toSVG(matrix, qrConfig.getForeColor(), qrConfig.getBackColor(),qrConfig.img,qrConfig.getRatio());
}
/**
* BitMatrix转SVG(字符串)
*
* @param matrix BitMatrix
* @param foreColor 前景色
* @param backColor 背景色(null表示透明背景)
* @param ratio 二维码中的Logo缩放的比例系数如5表示长宽最小值的1/5
* @return SVG矢量图字符串
* @since 5.8.6
*/
public static String toSVG(BitMatrix matrix,int foreColor, Integer backColor,Image logoImg,int ratio) {
StringBuilder sb = new StringBuilder();
int qrWidth = matrix.getWidth();
int qrHeight = matrix.getHeight();
int moduleHeight = (qrHeight == 1) ? qrWidth / 2 : 1;
for (int y = 0; y < qrHeight; y++) {
for (int x = 0; x < qrWidth; x++) {
if (matrix.get(x, y)) {
sb.append(" M" + x + "," + y + "h1v" + moduleHeight + "h-1z");
}
}
}
qrHeight *= moduleHeight;
String logoBase64 = "";
int logoWidth = 0;
int logoHeight = 0;
int logoX = 0;
int logoY = 0;
if (logoImg != null) {
logoBase64 = ImgUtil.toBase64DataUri(logoImg, "png");
// 按照最短的边做比例缩放
if (qrWidth < qrHeight) {
logoWidth = qrWidth / ratio;
logoHeight = logoImg.getHeight(null) * logoWidth / logoImg.getWidth(null);
} else {
logoHeight = qrHeight / ratio;
logoWidth = logoImg.getWidth(null) * logoHeight / logoImg.getHeight(null);
}
logoX = (qrWidth - logoWidth) / 2;
logoY = (qrHeight - logoHeight) / 2;
}
Color fore = new Color(foreColor, true);
Color back = new Color(backColor, true);
return "<svg width=\"" + qrWidth + "\" height=\"" + qrHeight + "\" \n" +
(backColor == null ? "" : "style=\"background-color:rgba(" + back.getRed() + "," + back.getGreen() + "," + back.getBlue() + "," + back.getAlpha() + ")\"\n") +
"viewBox=\"0 0 " + qrWidth + " " + qrHeight + "\" \n" +
"xmlns=\"http://www.w3.org/2000/svg\" \n" +
"xmlns:xlink=\"http://www.w3.org/1999/xlink\" >\n" +
"<path d=\"" + sb + "\" stroke=\"rgba(" + fore.getRed() + "," + fore.getGreen() + "," + fore.getBlue() + "," + fore.getAlpha() + ")\" /> \n" +
(StrUtil.isBlank(logoBase64) ? "" : "<image xlink:href=\"" + logoBase64 + "\" height=\"" + logoHeight + "\" width=\"" + logoWidth + "\" y=\"" + logoY + "\" x=\"" + logoX + "\" />\n") +
"</svg>";
}
/**
* 创建解码选项
*
* @param isTryHarder 是否优化精度
* @param isPureBarcode 是否使用复杂模式扫描带logo的二维码设为true
* @return 选项Map
*/
private static Map<DecodeHintType, Object> buildHints(boolean isTryHarder, boolean isPureBarcode) {
final HashMap<DecodeHintType, Object> hints = new HashMap<>();
hints.put(DecodeHintType.CHARACTER_SET, CharsetUtil.UTF_8);
// 优化精度
if (isTryHarder) {
hints.put(DecodeHintType.TRY_HARDER, true);
}
// 复杂模式开启PURE_BARCODE模式
if (isPureBarcode) {
hints.put(DecodeHintType.PURE_BARCODE, true);
}
return hints;
}
/**
* 解码多种类型的码,包括二维码和条形码
*
* @param formatReader {@link MultiFormatReader}
* @param binarizer {@link Binarizer}
* @return {@link Result}
*/
private static Result _decode(MultiFormatReader formatReader, Binarizer binarizer) {
try {
return formatReader.decodeWithState(new BinaryBitmap(binarizer));
} catch (NotFoundException e) {
return null;
}
}
}