qrcode move to swing

This commit is contained in:
Looly
2025-01-02 13:40:43 +08:00
parent 14445ccc77
commit 846e621172
16 changed files with 24 additions and 25 deletions

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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
}

View File

@@ -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">ECIExtended_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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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//不透明
);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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"));
}
}

View File

@@ -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"));
}
}

View File

@@ -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);
}
}

View 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);
}
}