mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-08-18 20:38:02 +08:00
qrcode move to swing
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
<Automatic-Module-Name>org.dromara.hutool.swing</Automatic-Module-Name>
|
||||
<animated-gif-lib.version>1.4</animated-gif-lib.version>
|
||||
<metadata-extractor.version>2.19.0</metadata-extractor.version>
|
||||
<zxing.version>3.5.3</zxing.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -57,6 +58,14 @@
|
||||
<version>${metadata-extractor.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- 二维码 -->
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>${zxing.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- UI主题库 -->
|
||||
<dependency>
|
||||
<groupId>com.formdev</groupId>
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
|
||||
import com.google.zxing.LuminanceSource;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* {@link BufferedImage} 图片二维码源<br>
|
||||
* 来自:http://blog.csdn.net/yangxin_blog/article/details/50850701<br>
|
||||
* 此类同样在zxing-j2se包中也有提供
|
||||
*
|
||||
* @author zxing, Looly
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public final class BufferedImageLuminanceSource extends LuminanceSource {
|
||||
|
||||
private final BufferedImage image;
|
||||
private final int left;
|
||||
private final int top;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param image {@link BufferedImage}
|
||||
*/
|
||||
public BufferedImageLuminanceSource(final BufferedImage image) {
|
||||
this(image, 0, 0, image.getWidth(), image.getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param image {@link BufferedImage}
|
||||
* @param left 左边间隔
|
||||
* @param top 顶部间隔
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
*/
|
||||
public BufferedImageLuminanceSource(final BufferedImage image, final int left, final int top, final int width, final int height) {
|
||||
super(width, height);
|
||||
|
||||
final int sourceWidth = image.getWidth();
|
||||
final 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(final int y, byte[] row) {
|
||||
if (y < 0 || y >= getHeight()) {
|
||||
throw new IllegalArgumentException("Requested row is outside the image: " + y);
|
||||
}
|
||||
final 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() {
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
final int area = width * height;
|
||||
final 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(final int left, final int top, final int width, final int height) {
|
||||
return new BufferedImageLuminanceSource(image, this.left + left, this.top + top, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRotateSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
@Override
|
||||
public LuminanceSource rotateCounterClockwise() {
|
||||
|
||||
final int sourceWidth = image.getWidth();
|
||||
final int sourceHeight = image.getHeight();
|
||||
|
||||
final AffineTransform transform = new AffineTransform(0.0, -1.0, 1.0, 0.0, 0.0, sourceWidth);
|
||||
|
||||
final BufferedImage rotatedImage = new BufferedImage(sourceHeight, sourceWidth, BufferedImage.TYPE_BYTE_GRAY);
|
||||
|
||||
final Graphics2D g = rotatedImage.createGraphics();
|
||||
g.drawImage(image, transform, null);
|
||||
g.dispose();
|
||||
|
||||
final int width = getWidth();
|
||||
return new BufferedImageLuminanceSource(rotatedImage, top, sourceWidth - (left + width), getHeight(), width);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
|
||||
import org.dromara.hutool.core.lang.ansi.AnsiElement;
|
||||
import org.dromara.hutool.core.lang.ansi.AnsiEncoder;
|
||||
import org.dromara.hutool.swing.img.color.ColorUtil;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
/**
|
||||
* 二维码的AsciiArt表示
|
||||
*
|
||||
* @author Tom Xin
|
||||
*/
|
||||
public class QrAsciiArt {
|
||||
|
||||
private final BitMatrix matrix;
|
||||
private final QrConfig qrConfig;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param matrix {@link BitMatrix}
|
||||
* @param qrConfig {@link QrConfig}
|
||||
*/
|
||||
public QrAsciiArt(final BitMatrix matrix, final QrConfig qrConfig) {
|
||||
this.matrix = matrix;
|
||||
this.qrConfig = qrConfig;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnnecessaryUnicodeEscape")
|
||||
@Override
|
||||
public String toString() {
|
||||
final int width = matrix.getWidth();
|
||||
final int height = matrix.getHeight();
|
||||
|
||||
|
||||
final AnsiElement foreground = qrConfig.foreColor == null ? null : ColorUtil.toAnsiColor(qrConfig.foreColor, true, false);
|
||||
final AnsiElement background = qrConfig.backColor == null ? null : ColorUtil.toAnsiColor(qrConfig.backColor, true, true);
|
||||
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i <= height; i += 2) {
|
||||
final StringBuilder rowBuilder = new StringBuilder();
|
||||
for (int j = 0; j < width; j++) {
|
||||
final boolean tp = matrix.get(i, j);
|
||||
final boolean bt = i + 1 >= height || matrix.get(i + 1, j);
|
||||
if (tp && bt) {
|
||||
rowBuilder.append(' ');//'\u0020'
|
||||
} else if (tp) {
|
||||
rowBuilder.append('▄');//'\u2584'
|
||||
} else if (bt) {
|
||||
rowBuilder.append('▀');//'\u2580'
|
||||
} else {
|
||||
rowBuilder.append('█');//'\u2588'
|
||||
}
|
||||
}
|
||||
builder.append(AnsiEncoder.encode(foreground, background, rowBuilder)).append('\n');
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
|
||||
import org.dromara.hutool.core.exception.HutoolException;
|
||||
|
||||
/**
|
||||
* Qrcode异常
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class QrCodeException extends HutoolException {
|
||||
private static final long serialVersionUID = 8247610319171014183L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param e 异常
|
||||
*/
|
||||
public QrCodeException(final Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param message 消息
|
||||
*/
|
||||
public QrCodeException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param messageTemplate 消息模板
|
||||
* @param params 参数
|
||||
*/
|
||||
public QrCodeException(final String messageTemplate, final Object... params) {
|
||||
super(messageTemplate, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param message 消息
|
||||
* @param cause 被包装的子异常
|
||||
*/
|
||||
public QrCodeException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param message 消息
|
||||
* @param cause 被包装的子异常
|
||||
* @param enableSuppression 是否启用抑制
|
||||
* @param writableStackTrace 堆栈跟踪是否应该是可写的
|
||||
*/
|
||||
public QrCodeException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param cause 被包装的子异常
|
||||
* @param messageTemplate 消息模板
|
||||
* @param params 参数
|
||||
*/
|
||||
public QrCodeException(final Throwable cause, final String messageTemplate, final Object... params) {
|
||||
super(cause, messageTemplate, params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.common.GlobalHistogramBinarizer;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
import org.dromara.hutool.core.codec.binary.Base64;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.io.file.FileNameUtil;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.net.url.UrlUtil;
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
import org.dromara.hutool.swing.img.ImgUtil;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
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 {
|
||||
|
||||
/**
|
||||
* SVG矢量图格式
|
||||
*/
|
||||
public static final String QR_TYPE_SVG = "svg";
|
||||
/**
|
||||
* Ascii Art字符画文本
|
||||
*/
|
||||
public static final String QR_TYPE_TXT = "txt";
|
||||
|
||||
/**
|
||||
* 生成 Base64 编码格式的二维码,以 String 形式表示
|
||||
*
|
||||
* <p>
|
||||
* 输出格式为: data:image/[type];base64,[data]
|
||||
* </p>
|
||||
*
|
||||
* @param content 内容
|
||||
* @param qrConfig 二维码配置,包括宽度、高度、边距、颜色等
|
||||
* @param imageType 类型(图片扩展名),见{@link #QR_TYPE_SVG}、 {@link #QR_TYPE_TXT}、{@link ImgUtil}
|
||||
* @return 图片 Base64 编码字符串
|
||||
*/
|
||||
public static String generateAsBase64DataUri(final String content, final QrConfig qrConfig, final String imageType) {
|
||||
switch (imageType) {
|
||||
case QR_TYPE_SVG:
|
||||
return svgToBase64DataUri(generateAsSvg(content, qrConfig));
|
||||
case QR_TYPE_TXT:
|
||||
return txtToBase64DataUri(generateAsAsciiArt(content, qrConfig));
|
||||
default:
|
||||
BufferedImage img = null;
|
||||
try {
|
||||
img = generate(content, qrConfig);
|
||||
return ImgUtil.toBase64DataUri(img, imageType);
|
||||
} finally {
|
||||
ImgUtil.flush(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成PNG格式的二维码图片,以byte[]形式表示
|
||||
*
|
||||
* @param content 内容
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
* @return 图片的byte[]
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public static byte[] generatePng(final String content, final int width, final 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(final String content, final QrConfig config) {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
generate(content, config, ImgUtil.IMAGE_TYPE_PNG, out);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成二维码到文件,二维码图片格式取决于文件的扩展名
|
||||
*
|
||||
* @param content 文本内容
|
||||
* @param width 宽度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小)
|
||||
* @param height 高度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小)
|
||||
* @param targetFile 目标文件,扩展名决定输出格式
|
||||
* @return 目标文件
|
||||
*/
|
||||
public static File generate(final String content, final int width, final int height, final File targetFile) {
|
||||
return generate(content, QrConfig.of(width, height), targetFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成二维码到文件,二维码图片格式取决于文件的扩展名
|
||||
*
|
||||
* @param content 文本内容
|
||||
* @param config 二维码配置,包括宽度、高度、边距、颜色等
|
||||
* @param targetFile 目标文件,扩展名决定输出格式
|
||||
* @return 目标文件
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public static File generate(final String content, final QrConfig config, final File targetFile) {
|
||||
final String extName = FileNameUtil.extName(targetFile);
|
||||
switch (extName) {
|
||||
case QR_TYPE_SVG:
|
||||
FileUtil.writeUtf8String(generateAsSvg(content, config), targetFile);
|
||||
break;
|
||||
case QR_TYPE_TXT:
|
||||
FileUtil.writeUtf8String(generateAsAsciiArt(content, config), targetFile);
|
||||
break;
|
||||
default:
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = generate(content, config);
|
||||
ImgUtil.write(image, targetFile);
|
||||
} finally {
|
||||
ImgUtil.flush(image);
|
||||
}
|
||||
}
|
||||
|
||||
return targetFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成二维码到输出流
|
||||
*
|
||||
* @param content 文本内容
|
||||
* @param width 宽度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小)
|
||||
* @param height 高度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小)
|
||||
* @param imageType 类型(图片扩展名),见{@link #QR_TYPE_SVG}、 {@link #QR_TYPE_TXT}、{@link ImgUtil}
|
||||
* @param out 目标流
|
||||
*/
|
||||
public static void generate(final String content, final int width, final int height, final String imageType, final OutputStream out) {
|
||||
generate(content, QrConfig.of(width, height), imageType, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成二维码到输出流
|
||||
*
|
||||
* @param content 文本内容
|
||||
* @param config 二维码配置,包括宽度、高度、边距、颜色等
|
||||
* @param imageType 图片类型(图片扩展名),见{@link ImgUtil}
|
||||
* @param out 目标流
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public static void generate(final String content, final QrConfig config, final String imageType, final OutputStream out) {
|
||||
switch (imageType) {
|
||||
case QR_TYPE_SVG:
|
||||
IoUtil.writeUtf8(out, false, generateAsSvg(content, config));
|
||||
break;
|
||||
case QR_TYPE_TXT:
|
||||
IoUtil.writeUtf8(out, false, generateAsAsciiArt(content, config));
|
||||
break;
|
||||
default:
|
||||
BufferedImage img = null;
|
||||
try {
|
||||
img = generate(content, config);
|
||||
ImgUtil.write(img, imageType, out);
|
||||
} finally {
|
||||
ImgUtil.flush(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成二维码图片
|
||||
*
|
||||
* @param content 文本内容
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
* @return 二维码图片(黑白)
|
||||
*/
|
||||
public static BufferedImage generate(final String content, final int width, final int height) {
|
||||
return generate(content, QrConfig.of(width, height));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成二维码或条形码图片<br>
|
||||
* 只有二维码时QrConfig中的图片才有效
|
||||
*
|
||||
* @param content 文本内容
|
||||
* @param config 二维码配置,包括宽度、高度、边距、颜色等
|
||||
* @return 二维码图片(黑白)
|
||||
* @since 4.1.14
|
||||
*/
|
||||
public static BufferedImage generate(final String content, final QrConfig config) {
|
||||
return new QrImage(content, ObjUtil.defaultIfNull(config, QrConfig::new));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------------- encode
|
||||
|
||||
/**
|
||||
* 将文本内容编码为条形码或二维码
|
||||
*
|
||||
* @param content 文本内容
|
||||
* @param config 二维码配置,包括宽度、高度、边距、颜色、格式等
|
||||
* @return {@link BitMatrix}
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public static BitMatrix encode(final CharSequence content, final QrConfig config) {
|
||||
return QrEncoder.of(config).encode(content);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------------- decode
|
||||
|
||||
/**
|
||||
* 解码二维码或条形码图片为文本
|
||||
*
|
||||
* @param qrCodeInputstream 二维码输入流
|
||||
* @return 解码文本
|
||||
*/
|
||||
public static String decode(final InputStream qrCodeInputstream) {
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = ImgUtil.read(qrCodeInputstream);
|
||||
return decode(image);
|
||||
} finally {
|
||||
ImgUtil.flush(image);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码二维码或条形码图片为文本
|
||||
*
|
||||
* @param qrCodeFile 二维码文件
|
||||
* @return 解码文本
|
||||
*/
|
||||
public static String decode(final File qrCodeFile) {
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = ImgUtil.read(qrCodeFile);
|
||||
return decode(image);
|
||||
} finally {
|
||||
ImgUtil.flush(image);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将二维码或条形码图片解码为文本
|
||||
*
|
||||
* @param image {@link Image} 二维码图片
|
||||
* @return 解码后的文本
|
||||
*/
|
||||
public static String decode(final 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(final Image image, final boolean isTryHarder, final boolean isPureBarcode) {
|
||||
return QrDecoder.of(isTryHarder, isPureBarcode).decode(image);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将二维码或条形码图片解码为文本<br>
|
||||
* 此方法会尝试使用{@link HybridBinarizer}和{@link GlobalHistogramBinarizer}两种模式解析<br>
|
||||
* 需要注意部分二维码如果不带logo,使用PureBarcode模式会解析失败,此时须设置此选项为false。
|
||||
*
|
||||
* @param image {@link Image} 二维码图片
|
||||
* @param hints 自定义扫码配置,包括算法、编码、复杂模式等
|
||||
* @return 解码后的文本
|
||||
* @since 5.7.12
|
||||
*/
|
||||
public static String decode(final Image image, final Map<DecodeHintType, Object> hints) {
|
||||
return QrDecoder.of(hints).decode(image);
|
||||
}
|
||||
|
||||
/**
|
||||
* BitMatrix转BufferedImage
|
||||
*
|
||||
* @param matrix BitMatrix
|
||||
* @param foreColor 前景色
|
||||
* @param backColor 背景色(null表示透明背景)
|
||||
* @return BufferedImage
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public static BufferedImage toImage(final BitMatrix matrix, final int foreColor, final Integer backColor) {
|
||||
final int width = matrix.getWidth();
|
||||
final int height = matrix.getHeight();
|
||||
final 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 content 内容
|
||||
* @param qrConfig 二维码配置,包括宽度、高度、边距、颜色等
|
||||
* @return SVG矢量图(字符串)
|
||||
* @since 5.8.6
|
||||
*/
|
||||
public static String generateAsSvg(final String content, final QrConfig qrConfig) {
|
||||
return toSVG(encode(content, qrConfig), qrConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* BitMatrix转SVG(字符串)
|
||||
*
|
||||
* @param matrix BitMatrix
|
||||
* @param config {@link QrConfig}
|
||||
* @return SVG矢量图(字符串)
|
||||
*/
|
||||
public static String toSVG(final BitMatrix matrix, final QrConfig config) {
|
||||
return new QrSVG(matrix, config).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成ASCII Art字符画形式的二维码
|
||||
*
|
||||
* @param content 内容
|
||||
* @param qrConfig 二维码配置,仅宽度、高度、边距配置有效
|
||||
* @return ASCII Art字符画形式的二维码
|
||||
* @since 5.8.6
|
||||
*/
|
||||
public static String generateAsAsciiArt(final String content, final QrConfig qrConfig) {
|
||||
return toAsciiArt(encode(content, qrConfig), qrConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* BitMatrix转ASCII Art字符画形式的二维码
|
||||
*
|
||||
* @param bitMatrix BitMatrix
|
||||
* @param qrConfig QR设置
|
||||
* @return ASCII Art字符画形式的二维码
|
||||
* @since 5.8.6
|
||||
*/
|
||||
public static String toAsciiArt(final BitMatrix bitMatrix, final QrConfig qrConfig) {
|
||||
return new QrAsciiArt(bitMatrix, qrConfig).toString();
|
||||
}
|
||||
|
||||
// region ----- Private Methods
|
||||
|
||||
/**
|
||||
* 将文本转换为Base64编码的Data URI。
|
||||
*
|
||||
* @param txt 需要转换为Base64编码Data URI的文本。
|
||||
* @return 转换后的Base64编码Data URI字符串。
|
||||
*/
|
||||
private static String txtToBase64DataUri(final String txt) {
|
||||
return UrlUtil.getDataUriBase64("text/plain", Base64.encode(txt));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将SVG字符串转换为Base64数据URI格式。
|
||||
* <p>此方法通过将SVG内容编码为Base64,并将其封装在数据URI中,以便于在HTML或CSS中直接嵌入SVG图像。</p>
|
||||
*
|
||||
* @param svg SVG图像的内容,为字符串形式。
|
||||
* @return 转换后的Base64数据URI字符串,可用于直接在HTML或CSS中显示SVG图像。
|
||||
*/
|
||||
private static String svgToBase64DataUri(final String svg) {
|
||||
return UrlUtil.getDataUriBase64("image/svg+xml", Base64.encode(svg));
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.util.CharsetUtil;
|
||||
import org.dromara.hutool.swing.img.ImgUtil;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.datamatrix.encoder.SymbolShapeHint;
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Image;
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* 二维码设置
|
||||
*
|
||||
* @author Looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public class QrConfig {
|
||||
|
||||
private static final int BLACK = 0xFF000000;
|
||||
private static final int WHITE = 0xFFFFFFFF;
|
||||
|
||||
/**
|
||||
* 宽度
|
||||
*/
|
||||
protected int width;
|
||||
/**
|
||||
* 高度
|
||||
*/
|
||||
protected int height;
|
||||
/**
|
||||
* 前景色(二维码颜色)
|
||||
*/
|
||||
protected Integer foreColor = BLACK;
|
||||
/**
|
||||
* 背景色,默认白色,null表示透明
|
||||
*/
|
||||
protected Integer backColor = WHITE;
|
||||
/**
|
||||
* 边距0~4
|
||||
*/
|
||||
protected Integer margin = 2;
|
||||
/**
|
||||
* 设置二维码中的信息量,可设置0-40的整数
|
||||
*/
|
||||
protected Integer qrVersion;
|
||||
/**
|
||||
* 纠错级别
|
||||
*/
|
||||
protected ErrorCorrectionLevel errorCorrection = ErrorCorrectionLevel.M;
|
||||
|
||||
/**
|
||||
* 编码
|
||||
*/
|
||||
protected Charset charset = CharsetUtil.UTF_8;
|
||||
/**
|
||||
* 二维码中的Logo
|
||||
*/
|
||||
protected Image img;
|
||||
|
||||
/**
|
||||
* 二维码logo圆角弧度,0~1,为长宽占比
|
||||
*/
|
||||
protected double imgRound = 0.3;
|
||||
/**
|
||||
* 二维码中的Logo缩放的比例系数,如5表示长宽最小值的1/5
|
||||
*/
|
||||
protected int ratio = 6;
|
||||
/**
|
||||
* DATA_MATRIX的符号形状
|
||||
*/
|
||||
protected SymbolShapeHint shapeHint = SymbolShapeHint.FORCE_NONE;
|
||||
|
||||
/**
|
||||
* 生成码的格式,默认为二维码
|
||||
*/
|
||||
protected BarcodeFormat format = BarcodeFormat.QR_CODE;
|
||||
|
||||
/**
|
||||
* 创建QrConfig
|
||||
*
|
||||
* @return QrConfig
|
||||
* @since 4.1.14
|
||||
*/
|
||||
public static QrConfig of() {
|
||||
return new QrConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建QrConfig
|
||||
*
|
||||
* @param width 宽
|
||||
* @param height 高
|
||||
* @return QrConfig
|
||||
* @since 4.1.14
|
||||
*/
|
||||
public static QrConfig of(final int width, final int height) {
|
||||
return new QrConfig(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造,默认长宽为300
|
||||
*/
|
||||
public QrConfig() {
|
||||
this(300, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 宽
|
||||
* @param height 高
|
||||
*/
|
||||
public QrConfig(final int width, final int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取宽度
|
||||
*
|
||||
* @return 宽度
|
||||
*/
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置宽度
|
||||
*
|
||||
* @param width 宽度
|
||||
* @return this
|
||||
*/
|
||||
public QrConfig setWidth(final int width) {
|
||||
this.width = width;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取高度
|
||||
*
|
||||
* @return 高度
|
||||
*/
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置高度
|
||||
*
|
||||
* @param height 高度
|
||||
* @return this;
|
||||
*/
|
||||
public QrConfig setHeight(final int height) {
|
||||
this.height = height;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前景色
|
||||
*
|
||||
* @return 前景色
|
||||
*/
|
||||
public int getForeColor() {
|
||||
return foreColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置前景色,例如:Color.BLUE.getRGB()
|
||||
*
|
||||
* @param foreColor 前景色
|
||||
* @return this
|
||||
* @since 5.1.1
|
||||
*/
|
||||
public QrConfig setForeColor(final Color foreColor) {
|
||||
if (null == foreColor) {
|
||||
this.foreColor = null;
|
||||
} else {
|
||||
this.foreColor = foreColor.getRGB();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取背景色
|
||||
*
|
||||
* @return 背景色
|
||||
*/
|
||||
public int getBackColor() {
|
||||
return backColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置背景色,例如:Color.BLUE
|
||||
*
|
||||
* @param backColor 背景色,null表示透明背景
|
||||
* @return this
|
||||
* @since 5.1.1
|
||||
*/
|
||||
public QrConfig setBackColor(final Color backColor) {
|
||||
if (null == backColor) {
|
||||
this.backColor = null;
|
||||
} else {
|
||||
this.backColor = backColor.getRGB();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取边距
|
||||
*
|
||||
* @return 边距
|
||||
*/
|
||||
public Integer getMargin() {
|
||||
return margin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置边距
|
||||
*
|
||||
* @param margin 边距
|
||||
* @return this
|
||||
*/
|
||||
public QrConfig setMargin(final Integer margin) {
|
||||
this.margin = margin;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置二维码中的信息量,可设置0-40的整数,二维码图片也会根据qrVersion而变化,0表示根据传入信息自动变化
|
||||
*
|
||||
* @return 二维码中的信息量
|
||||
*/
|
||||
public Integer getQrVersion() {
|
||||
return qrVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置二维码中的信息量,可设置0-40的整数,二维码图片也会根据qrVersion而变化,0表示根据传入信息自动变化
|
||||
*
|
||||
* @param qrVersion 二维码中的信息量
|
||||
* @return this
|
||||
*/
|
||||
public QrConfig setQrVersion(final Integer qrVersion) {
|
||||
this.qrVersion = qrVersion;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取纠错级别
|
||||
*
|
||||
* @return 纠错级别
|
||||
*/
|
||||
public ErrorCorrectionLevel getErrorCorrection() {
|
||||
return errorCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否开启ECI编码<br>
|
||||
* 如果enableEci=false,则二维码中不包含ECI信息,即:{@link #charset}字符编码设置为{@code null}, 二维码为英文字符,保持false最佳<br>
|
||||
* 如果enableEci=true,则二维码中包含ECI信息,即:按照{@link #charset}编码进行设置, 二维码为包含中文,保持true最佳,否则会中文乱码<br>
|
||||
*
|
||||
* <ul>
|
||||
* <li>参考1:<a href="https://github.com/nutzam/nutz-qrcode/issues/6">关于\000026的问题</a></li>
|
||||
* <li>参考2:<a href="https://en.wikipedia.org/wiki/Extended_Channel_Interpretation">ECI(Extended_Channel_Interpretation)模式</a></li>
|
||||
* <li>参考3:<a href="https://www.51cto.com/article/414082.html">二维码的生成细节和原理</a></li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* 二维码编码有ECI模式和非ECI模式的情况之分,在ECI模式下第一个字节是用作编码标识,而非ECI模式下直接就是数据流。
|
||||
* ECI模式其实是更好的方案,这样子解码的时候可以根据标识采用不同的编码方式。而非ECI模式只能按照一种统一的方式处理了。
|
||||
* 但是由于部分设备不支持ECI模式,所以就出现了无法识别的情况。
|
||||
* 使用扫码桩/扫码枪,可能会出现\000026的字符。使用手机扫描、其他二维码解析软件扫描,则不会出现。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* ECI编码表可以看出UTF-8就是对应"\000026"(对应数字22)<br><br>
|
||||
* </p>
|
||||
*
|
||||
* <p> 总结建议:如果二维码内容全是字符,没有中文,就不用使用UTF-8等格式进行编码,只有使用中文等特殊符号才需要编码 </p>
|
||||
*
|
||||
* @param enableEci 是否开启ECI
|
||||
* @see EncodeHintType#PDF417_AUTO_ECI
|
||||
*/
|
||||
public void setEnableEci(final boolean enableEci) {
|
||||
if (enableEci) {
|
||||
if(null == this.charset){
|
||||
this.charset = CharsetUtil.UTF_8;
|
||||
}
|
||||
} else {
|
||||
this.charset = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置纠错级别
|
||||
*
|
||||
* @param errorCorrection 纠错级别
|
||||
* @return this
|
||||
*/
|
||||
public QrConfig setErrorCorrection(final ErrorCorrectionLevel errorCorrection) {
|
||||
this.errorCorrection = errorCorrection;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取编码
|
||||
*
|
||||
* @return 编码
|
||||
*/
|
||||
public Charset getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置编码
|
||||
*
|
||||
* @param charset 编码
|
||||
* @return this
|
||||
*/
|
||||
public QrConfig setCharset(final Charset charset) {
|
||||
this.charset = charset;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取二维码中的Logo
|
||||
*
|
||||
* @return Logo图片
|
||||
*/
|
||||
public Image getImg() {
|
||||
return img;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置二维码中的Logo文件
|
||||
*
|
||||
* @param imgPath 二维码中的Logo路径
|
||||
* @return this;
|
||||
*/
|
||||
public QrConfig setImg(final String imgPath) {
|
||||
return setImg(FileUtil.file(imgPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置二维码中的Logo文件
|
||||
*
|
||||
* @param imageBytes 二维码中的Logo图片bytes表示形式
|
||||
* @return this;
|
||||
*/
|
||||
public QrConfig setImg(final byte[] imageBytes) {
|
||||
return setImg(ImgUtil.toImage(imageBytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置二维码中的Logo文件
|
||||
*
|
||||
* @param imgFile 二维码中的Logo
|
||||
* @return this;
|
||||
*/
|
||||
public QrConfig setImg(final File imgFile) {
|
||||
return setImg(ImgUtil.read(imgFile));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置二维码中的Logo
|
||||
*
|
||||
* @param img 二维码中的Logo
|
||||
* @return this;
|
||||
*/
|
||||
public QrConfig setImg(final Image img) {
|
||||
this.img = img;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取二维码logo圆角弧度,0~1,为长宽占比
|
||||
* @return 二维码logo圆角弧度,0~1,为长宽占比
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public double getImgRound() {
|
||||
return imgRound;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置二维码logo圆角弧度,0~1,为长宽占比
|
||||
* @param imgRound 二维码logo圆角弧度,0~1,为长宽占比
|
||||
* @return this
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public QrConfig setImgRound(final double imgRound) {
|
||||
this.imgRound = imgRound;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取二维码中的Logo缩放的比例系数,如5表示长宽最小值的1/5
|
||||
*
|
||||
* @return 二维码中的Logo缩放的比例系数,如5表示长宽最小值的1/5
|
||||
*/
|
||||
public int getRatio() {
|
||||
return this.ratio;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置二维码中的Logo缩放的比例系数,如5表示长宽最小值的1/5
|
||||
*
|
||||
* @param ratio 二维码中的Logo缩放的比例系数,如5表示长宽最小值的1/5
|
||||
* @return this;
|
||||
*/
|
||||
public QrConfig setRatio(final int ratio) {
|
||||
this.ratio = ratio;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置DATA_MATRIX的符号形状
|
||||
*
|
||||
* @param shapeHint DATA_MATRIX的符号形状
|
||||
* @return this
|
||||
*/
|
||||
public QrConfig setShapeHint(final SymbolShapeHint shapeHint) {
|
||||
this.shapeHint = shapeHint;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取码格式
|
||||
*
|
||||
* @return 码格式,默认为二维码
|
||||
*/
|
||||
public BarcodeFormat getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置码格式,默认二维码
|
||||
*
|
||||
* @param format 码格式
|
||||
* @return this
|
||||
*/
|
||||
public QrConfig setFormat(final BarcodeFormat format) {
|
||||
this.format = format;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 转换为Zxing的二维码配置
|
||||
*
|
||||
* @return 配置
|
||||
*/
|
||||
public HashMap<EncodeHintType, Object> toHints() {
|
||||
// 配置
|
||||
final HashMap<EncodeHintType, Object> hints = new HashMap<>();
|
||||
// 只有不禁用(即开启)ECI编码功能,才使用自定义的字符编码
|
||||
// 二维码内容就是英文字符,建议不设置编码,没有任何问题;对于中文来说,会乱码
|
||||
if (null != this.charset) {
|
||||
hints.put(EncodeHintType.CHARACTER_SET, charset.toString().toLowerCase());
|
||||
}
|
||||
if (null != this.errorCorrection) {
|
||||
final Object value;
|
||||
if (BarcodeFormat.AZTEC == format || BarcodeFormat.PDF_417 == format) {
|
||||
// issue#I4FE3U@Gitee
|
||||
value = this.errorCorrection.getBits();
|
||||
} else {
|
||||
value = this.errorCorrection;
|
||||
}
|
||||
|
||||
hints.put(EncodeHintType.ERROR_CORRECTION, value);
|
||||
hints.put(EncodeHintType.DATA_MATRIX_SHAPE, shapeHint);
|
||||
}
|
||||
if (null != this.margin) {
|
||||
hints.put(EncodeHintType.MARGIN, this.margin);
|
||||
}
|
||||
if (null != this.qrVersion) {
|
||||
hints.put(EncodeHintType.QR_VERSION, this.qrVersion);
|
||||
}
|
||||
return hints;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
|
||||
import org.dromara.hutool.core.codec.Decoder;
|
||||
import org.dromara.hutool.core.util.CharsetUtil;
|
||||
import org.dromara.hutool.swing.img.ImgUtil;
|
||||
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.NotFoundException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.GlobalHistogramBinarizer;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 二维码(条形码等)解码器
|
||||
*
|
||||
* @author Looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class QrDecoder implements Decoder<Image, String> {
|
||||
|
||||
private final Map<DecodeHintType, Object> hints;
|
||||
|
||||
/**
|
||||
* 创建二维码(条形码等)解码器,用于将二维码(条形码等)解码为所代表的内容字符串
|
||||
*
|
||||
* @param isTryHarder 是否优化精度
|
||||
* @param isPureBarcode 是否使用复杂模式,扫描带logo的二维码设为true
|
||||
* @return QrDecoder
|
||||
*/
|
||||
public static QrDecoder of(final boolean isTryHarder, final boolean isPureBarcode) {
|
||||
return of(buildHints(isTryHarder, isPureBarcode));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建二维码(条形码等)解码器
|
||||
*
|
||||
* @param hints 自定义扫码配置,包括算法、编码、复杂模式等
|
||||
* @return QrDecoder
|
||||
*/
|
||||
public static QrDecoder of(final Map<DecodeHintType, Object> hints) {
|
||||
return new QrDecoder(hints);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param hints 自定义扫码配置,包括算法、编码、复杂模式等
|
||||
*/
|
||||
public QrDecoder(final Map<DecodeHintType, Object> hints) {
|
||||
this.hints = hints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decode(final Image image) {
|
||||
final MultiFormatReader formatReader = new MultiFormatReader();
|
||||
formatReader.setHints(hints);
|
||||
|
||||
final LuminanceSource source = new BufferedImageLuminanceSource(
|
||||
ImgUtil.castToBufferedImage(image, ImgUtil.IMAGE_TYPE_JPG));
|
||||
|
||||
Result result = _decode(formatReader, new HybridBinarizer(source));
|
||||
if (null == result) {
|
||||
result = _decode(formatReader, new GlobalHistogramBinarizer(source));
|
||||
}
|
||||
|
||||
return null != result ? result.getText() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码多种类型的码,包括二维码和条形码
|
||||
*
|
||||
* @param formatReader {@link MultiFormatReader}
|
||||
* @param binarizer {@link Binarizer}
|
||||
* @return {@link Result}
|
||||
*/
|
||||
private static Result _decode(final MultiFormatReader formatReader, final Binarizer binarizer) {
|
||||
try {
|
||||
return formatReader.decodeWithState(new BinaryBitmap(binarizer));
|
||||
} catch (final NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建解码选项
|
||||
*
|
||||
* @param isTryHarder 是否优化精度
|
||||
* @param isPureBarcode 是否使用复杂模式,扫描带logo的二维码设为true
|
||||
* @return 选项Map
|
||||
*/
|
||||
private static Map<DecodeHintType, Object> buildHints(final boolean isTryHarder, final boolean isPureBarcode) {
|
||||
final HashMap<DecodeHintType, Object> hints = new HashMap<>(3, 1);
|
||||
hints.put(DecodeHintType.CHARACTER_SET, CharsetUtil.NAME_UTF_8);
|
||||
|
||||
// 优化精度
|
||||
if (isTryHarder) {
|
||||
hints.put(DecodeHintType.TRY_HARDER, true);
|
||||
}
|
||||
// 复杂模式,开启PURE_BARCODE模式
|
||||
if (isPureBarcode) {
|
||||
hints.put(DecodeHintType.PURE_BARCODE, true);
|
||||
}
|
||||
return hints;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
|
||||
import org.dromara.hutool.core.codec.Encoder;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
/**
|
||||
* 二维码(条形码等)编码器,用于将文本内容转换为二维码
|
||||
*
|
||||
* @author Looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class QrEncoder implements Encoder<CharSequence, BitMatrix> {
|
||||
|
||||
/**
|
||||
* 创建QrEncoder
|
||||
*
|
||||
* @param config {@link QrConfig}
|
||||
* @return QrEncoder
|
||||
*/
|
||||
public static QrEncoder of(final QrConfig config) {
|
||||
return new QrEncoder(config);
|
||||
}
|
||||
|
||||
private final QrConfig config;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param config {@link QrConfig}
|
||||
*/
|
||||
public QrEncoder(final QrConfig config) {
|
||||
this.config = ObjUtil.defaultIfNull(config, QrConfig::of);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitMatrix encode(final CharSequence content) {
|
||||
final MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
|
||||
|
||||
final BitMatrix bitMatrix;
|
||||
try {
|
||||
bitMatrix = multiFormatWriter.encode(
|
||||
StrUtil.toString(content),
|
||||
config.format, config.width, config.height,
|
||||
config.toHints());
|
||||
} catch (final WriterException e) {
|
||||
throw new QrCodeException(e);
|
||||
}
|
||||
|
||||
return bitMatrix;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
|
||||
import org.dromara.hutool.swing.img.Img;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* 二维码图片封装
|
||||
*
|
||||
* @author Looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class QrImage extends BufferedImage {
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param content 文本内容
|
||||
* @param config {@link QrConfig} 二维码配置,包括宽度、高度、边距、颜色、格式等
|
||||
*/
|
||||
public QrImage(final String content, final QrConfig config) {
|
||||
this(QrCodeUtil.encode(content, config), config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param matrix {@link BitMatrix}
|
||||
* @param config {@link QrConfig},非空
|
||||
*/
|
||||
public QrImage(final BitMatrix matrix, final QrConfig config) {
|
||||
super(matrix.getWidth(), matrix.getHeight(), null == config.backColor ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
|
||||
init(matrix, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
* @param matrix {@link BitMatrix}
|
||||
* @param config {@link QrConfig}
|
||||
*/
|
||||
private void init(final BitMatrix matrix, final QrConfig config) {
|
||||
final int width = matrix.getWidth();
|
||||
final int height = matrix.getHeight();
|
||||
final Integer foreColor = config.foreColor;
|
||||
final Integer backColor = config.backColor;
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
if (matrix.get(x, y)) {
|
||||
setRGB(x, y, foreColor);
|
||||
} else if (null != backColor) {
|
||||
setRGB(x, y, backColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Image logoImg = config.img;
|
||||
if (null != logoImg && BarcodeFormat.QR_CODE == config.format) {
|
||||
// 只有二维码可以贴图
|
||||
final int qrWidth = getWidth();
|
||||
final int qrHeight = getHeight();
|
||||
final int imgWidth;
|
||||
final int imgHeight;
|
||||
// 按照最短的边做比例缩放
|
||||
if (qrWidth < qrHeight) {
|
||||
imgWidth = qrWidth / config.ratio;
|
||||
imgHeight = logoImg.getHeight(null) * imgWidth / logoImg.getWidth(null);
|
||||
} else {
|
||||
imgHeight = qrHeight / config.ratio;
|
||||
imgWidth = logoImg.getWidth(null) * imgHeight / logoImg.getHeight(null);
|
||||
}
|
||||
|
||||
// 原图片上直接绘制水印
|
||||
Img.from(this).pressImage(//
|
||||
Img.from(logoImg).round(config.imgRound).getImg(), // 圆角
|
||||
new Rectangle(imgWidth, imgHeight), // 位置
|
||||
1//不透明
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.swing.img.ImgUtil;
|
||||
import org.dromara.hutool.swing.img.color.ColorUtil;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Image;
|
||||
|
||||
/**
|
||||
* 二维码的SVG表示
|
||||
*
|
||||
* @author Tom Xin
|
||||
*/
|
||||
public class QrSVG {
|
||||
|
||||
private final BitMatrix matrix;
|
||||
private final QrConfig qrConfig;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param matrix {@link BitMatrix}
|
||||
* @param qrConfig {@link QrConfig}
|
||||
*/
|
||||
public QrSVG(final BitMatrix matrix, final QrConfig qrConfig) {
|
||||
this.matrix = matrix;
|
||||
this.qrConfig = qrConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final Image logoImg = qrConfig.img;
|
||||
final Integer foreColor = qrConfig.foreColor;
|
||||
final Integer backColor = qrConfig.backColor;
|
||||
final int ratio = qrConfig.ratio;
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final int qrWidth = matrix.getWidth();
|
||||
int qrHeight = matrix.getHeight();
|
||||
final 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").append(x).append(",").append(y).append("h1v").append(moduleHeight).append("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;
|
||||
|
||||
}
|
||||
|
||||
final StringBuilder result = StrUtil.builder();
|
||||
result.append("<svg width=\"").append(qrWidth).append("\" height=\"").append(qrHeight).append("\" \n");
|
||||
if (backColor != null) {
|
||||
final Color back = new Color(backColor, true);
|
||||
result.append("style=\"background-color:").append(ColorUtil.toCssRgba(back)).append("\"\n");
|
||||
}
|
||||
result.append("viewBox=\"0 0 ").append(qrWidth).append(" ").append(qrHeight).append("\" \n");
|
||||
result.append("xmlns=\"http://www.w3.org/2000/svg\" \n");
|
||||
result.append("xmlns:xlink=\"http://www.w3.org/1999/xlink\" >\n");
|
||||
result.append("<path d=\"").append(sb).append("\" ");
|
||||
if (foreColor != null) {
|
||||
final Color fore = new Color(foreColor, true);
|
||||
result.append("stroke=\"").append(ColorUtil.toCssRgba(fore)).append("\"");
|
||||
}
|
||||
result.append(" /> \n");
|
||||
if (StrUtil.isNotBlank(logoBase64)) {
|
||||
result.append("<image xlink:href=\"").append(logoBase64).append("\" height=\"").append(logoHeight).append("\" width=\"").append(logoWidth).append("\" y=\"").append(logoY).append("\" x=\"").append(logoX).append("\" />\n");
|
||||
}
|
||||
result.append("</svg>");
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 二维码封装,基于zxing库,入口为QrCodeUtil
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.dromara.hutool.core.io.file.FileUtil.file;
|
||||
|
||||
public class Discussions3030Test {
|
||||
@Test
|
||||
@Disabled
|
||||
public void name() {
|
||||
//扫描二维码后 对应的链接正常
|
||||
final String path = "https://juejin.cn/backend?name=%E5%BC%A0%E7%8F%8A&school=%E5%8E%A6%E9%97%A8%E5%A4%A7%E5%AD%A6";
|
||||
QrCodeUtil.generate(path, QrConfig.of(), file("d:/test/3030.png"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class Issue3146Test {
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
void generateTest() {
|
||||
QrCodeUtil.generate("https://www.baidu.com/h5/monitorfile/index.html?sadfsfasdfsafsafasfasfsafasfasdfsafdsafsfasfafsfaasfsdfsfsafasfa",
|
||||
QrConfig.of().setWidth(600).setHeight(600).setMargin(0), new File("d:/test/issue3146.jpg"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class IssuesI76SZBTest {
|
||||
@Test
|
||||
@Disabled
|
||||
void generateTest() {
|
||||
final QrConfig qrConfig = new QrConfig(300, 300);
|
||||
final File file = new File("d:/test/out.png");
|
||||
qrConfig.setImg(new File("d:/test/b2dd3614_868440.png"));
|
||||
QrCodeUtil.generate("111", qrConfig, file);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.swing.qrcode;
|
||||
|
||||
import org.dromara.hutool.core.codec.binary.Base64;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.lang.Console;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.swing.img.ImgUtil;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.datamatrix.encoder.SymbolShapeHint;
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 二维码工具类单元测试
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class QrCodeUtilTest {
|
||||
|
||||
@Test
|
||||
public void generateTest() {
|
||||
final BufferedImage image = QrCodeUtil.generate("https://hutool.cn/", 300, 300);
|
||||
Assertions.assertNotNull(image);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void generateCustomTest() {
|
||||
final QrConfig config = QrConfig.of();
|
||||
config.setMargin(0);
|
||||
config.setForeColor(Color.CYAN);
|
||||
// 关闭ECI模式,部分老设备不支持ECI编码; 如果二维码中包含中文,则需要必须开启此配置
|
||||
// 现象:使用三方识别二维码工具、手机微信、支付宝扫描正常,使用扫码桩、扫码枪识别会多出来,类似:\000026、\000029字符
|
||||
config.setEnableEci(false);
|
||||
// 背景色透明
|
||||
config.setBackColor(null);
|
||||
config.setErrorCorrection(ErrorCorrectionLevel.H);
|
||||
final String path = FileUtil.isWindows() ? "d:/test/qrcodeCustom.png" : "~/Desktop/hutool/qrcodeCustom.png";
|
||||
QrCodeUtil.generate("https://hutool.cn/", config, FileUtil.touch(path));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void generateWithLogoTest() {
|
||||
final String icon = FileUtil.isWindows() ? "d:/test/shape/logo.jpg" : "~/Desktop/hutool/shape/logo.jpg";
|
||||
final String targetPath = FileUtil.isWindows() ? "d:/test/qrcodeWithLogo.jpg" : "~/Desktop/hutool/qrcodeWithLogo.jpg";
|
||||
QrCodeUtil.generate(//
|
||||
"https://hutool.cn/", //
|
||||
QrConfig.of().setImg(icon), //
|
||||
FileUtil.touch(targetPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void decodeTest() {
|
||||
final String decode = QrCodeUtil.decode(FileUtil.file("d:/test/shape/qr.png"));
|
||||
Console.log(decode);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void decodeTest2() {
|
||||
// 条形码
|
||||
final String decode = QrCodeUtil.decode(FileUtil.file("d:/test/90.png"));
|
||||
Console.log(decode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateAsBase64AndDecodeTest() {
|
||||
final String url = "https://hutool.cn/";
|
||||
String base64 = QrCodeUtil.generateAsBase64DataUri(url, new QrConfig(400, 400), "png");
|
||||
Assertions.assertNotNull(base64);
|
||||
|
||||
base64 = StrUtil.removePrefix(base64, "data:image/png;base64,");
|
||||
final String decode = QrCodeUtil.decode(IoUtil.toStream(Base64.decode(base64)));
|
||||
Assertions.assertEquals(url, decode);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void generateAsBase64Test2() {
|
||||
final byte[] bytes = FileUtil.readBytes(new File("d:/test/qr.png"));
|
||||
final String base641 = QrCodeUtil.generateAsBase64DataUri(
|
||||
"https://hutool.cn/",
|
||||
QrConfig.of(400, 400).setImg(bytes),
|
||||
"png"
|
||||
);
|
||||
Assertions.assertNotNull(base641);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void decodeTest3() {
|
||||
final String decode = QrCodeUtil.decode(ImgUtil.read("d:/test/qr_a.png"), false, true);
|
||||
Console.log(decode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pdf417Test() {
|
||||
final BufferedImage image = QrCodeUtil.generate("content111", QrConfig.of().setFormat(BarcodeFormat.PDF_417));
|
||||
Assertions.assertNotNull(image);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateDataMatrixTest() {
|
||||
final QrConfig qrConfig = QrConfig.of();
|
||||
qrConfig.setShapeHint(SymbolShapeHint.FORCE_RECTANGLE);
|
||||
final BufferedImage image = QrCodeUtil.generate("content111", qrConfig.setFormat(BarcodeFormat.DATA_MATRIX));
|
||||
Assertions.assertNotNull(image);
|
||||
final QrConfig config = QrConfig.of();
|
||||
config.setShapeHint(SymbolShapeHint.FORCE_SQUARE);
|
||||
final BufferedImage imageSquare = QrCodeUtil.generate("content111", qrConfig.setFormat(BarcodeFormat.DATA_MATRIX));
|
||||
Assertions.assertNotNull(imageSquare);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void generateSvgTest() {
|
||||
final QrConfig qrConfig = QrConfig.of()
|
||||
.setImg("d:/test/shape/logo.jpg")
|
||||
.setForeColor(Color.blue)
|
||||
.setBackColor(Color.pink)
|
||||
.setRatio(8)
|
||||
.setErrorCorrection(ErrorCorrectionLevel.M)
|
||||
.setMargin(1);
|
||||
final String svg = QrCodeUtil.generateAsSvg("https://hutool.cn/", qrConfig);
|
||||
Assertions.assertNotNull(svg);
|
||||
FileUtil.writeString(svg, FileUtil.touch("d:/test/hutool_qr.svg"), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateAsciiArtTest() {
|
||||
final QrConfig qrConfig = QrConfig.of()
|
||||
.setForeColor(Color.BLUE)
|
||||
.setBackColor(Color.MAGENTA)
|
||||
.setWidth(0)
|
||||
.setHeight(0).setMargin(1);
|
||||
final String asciiArt = QrCodeUtil.generateAsAsciiArt("https://hutool.cn/", qrConfig);
|
||||
Assertions.assertNotNull(asciiArt);
|
||||
//Console.log(asciiArt);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateAsciiArtNoCustomColorTest() {
|
||||
final QrConfig qrConfig = QrConfig.of()
|
||||
.setForeColor(null)
|
||||
.setBackColor(null)
|
||||
.setWidth(0)
|
||||
.setHeight(0).setMargin(1);
|
||||
final String asciiArt = QrCodeUtil.generateAsAsciiArt("https://hutool.cn/", qrConfig);
|
||||
Assertions.assertNotNull(asciiArt);
|
||||
//Console.log(asciiArt);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user