diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrAsciiArt.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrAsciiArt.java
deleted file mode 100644
index 5029f1081..000000000
--- a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrAsciiArt.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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();
- }
-}
diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrCodeUtil.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrCodeUtil.java
index 5b8cf738d..0dcb07325 100644
--- a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrCodeUtil.java
+++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrCodeUtil.java
@@ -22,19 +22,19 @@ 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 org.dromara.hutool.swing.qrcode.render.AsciiArtRender;
+import org.dromara.hutool.swing.qrcode.render.BitMatrixRender;
+import org.dromara.hutool.swing.qrcode.render.ImageRender;
+import org.dromara.hutool.swing.qrcode.render.SVGRender;
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.io.*;
import java.util.Map;
/**
@@ -140,23 +140,11 @@ public class QrCodeUtil {
*/
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);
- }
+ try (final BufferedOutputStream outputStream = FileUtil.getOutputStream(targetFile)) {
+ generate(content, config, extName, outputStream);
+ } catch (final IOException e) {
+ throw new RuntimeException(e);
}
-
return targetFile;
}
@@ -183,22 +171,18 @@ public class QrCodeUtil {
* @since 4.1.2
*/
public static void generate(final String content, final QrConfig config, final String imageType, final OutputStream out) {
+ final BitMatrixRender render;
switch (imageType) {
case QR_TYPE_SVG:
- IoUtil.writeUtf8(out, false, generateAsSvg(content, config));
+ render = new SVGRender(config);
break;
case QR_TYPE_TXT:
- IoUtil.writeUtf8(out, false, generateAsAsciiArt(content, config));
+ render = new AsciiArtRender(config);
break;
default:
- BufferedImage img = null;
- try {
- img = generate(content, config);
- ImgUtil.write(img, imageType, out);
- } finally {
- ImgUtil.flush(img);
- }
+ render = new ImageRender(config, imageType);
}
+ render.render(encode(content, config), out);
}
/**
@@ -223,7 +207,7 @@ public class QrCodeUtil {
* @since 4.1.14
*/
public static BufferedImage generate(final String content, final QrConfig config) {
- return new QrImage(content, ObjUtil.defaultIfNull(config, QrConfig::new));
+ return new ImageRender(ObjUtil.defaultIfNull(config, QrConfig::new), null).render(encode(content, config));
}
// ------------------------------------------------------------------------------------------------------------------- encode
@@ -356,7 +340,9 @@ public class QrCodeUtil {
* @return SVG矢量图(字符串)
*/
public static String toSVG(final BitMatrix matrix, final QrConfig config) {
- return new QrSVG(matrix, config).toString();
+ final StringBuilder result = new StringBuilder();
+ new SVGRender(config).render(matrix, result);
+ return result.toString();
}
/**
@@ -374,13 +360,15 @@ public class QrCodeUtil {
/**
* BitMatrix转ASCII Art字符画形式的二维码
*
- * @param bitMatrix BitMatrix
- * @param qrConfig QR设置
+ * @param matrix BitMatrix
+ * @param config QR设置
* @return ASCII Art字符画形式的二维码
* @since 5.8.6
*/
- public static String toAsciiArt(final BitMatrix bitMatrix, final QrConfig qrConfig) {
- return new QrAsciiArt(bitMatrix, qrConfig).toString();
+ public static String toAsciiArt(final BitMatrix matrix, final QrConfig config) {
+ final StringBuilder result = new StringBuilder();
+ new AsciiArtRender(config).render(matrix, result);
+ return result.toString();
}
// region ----- Private Methods
diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrConfig.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrConfig.java
index 2522a606f..4d67d5592 100644
--- a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrConfig.java
+++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrConfig.java
@@ -182,7 +182,7 @@ public class QrConfig {
*
* @return 前景色
*/
- public int getForeColor() {
+ public Integer getForeColor() {
return foreColor;
}
@@ -207,7 +207,7 @@ public class QrConfig {
*
* @return 背景色
*/
- public int getBackColor() {
+ public Integer getBackColor() {
return backColor;
}
diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrImage.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrImage.java
deleted file mode 100644
index e2d0f3f71..000000000
--- a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrImage.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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//不透明
- );
- }
- }
-}
diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrSVG.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrSVG.java
deleted file mode 100644
index 9ff50442a..000000000
--- a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/QrSVG.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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("");
- return result.toString();
- }
-}
diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/AsciiArtRender.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/AsciiArtRender.java
new file mode 100644
index 000000000..e75573fd2
--- /dev/null
+++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/AsciiArtRender.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 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.render;
+
+import com.google.zxing.common.BitMatrix;
+import org.dromara.hutool.core.io.IORuntimeException;
+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 org.dromara.hutool.swing.qrcode.QrConfig;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+
+/**
+ * ASCII Art渲染
+ *
+ * @author Looly
+ * @since 6.0.0
+ */
+public class AsciiArtRender implements BitMatrixRender {
+
+ private final QrConfig config;
+
+ /**
+ * 构造
+ *
+ * @param config 二维码配置
+ */
+ public AsciiArtRender(final QrConfig config) {
+ this.config = config;
+ }
+
+ @Override
+ public void render(final BitMatrix matrix, final OutputStream out) {
+ render(matrix, new OutputStreamWriter(out, config.getCharset()));
+ }
+
+ /**
+ * 渲染SVG
+ *
+ * @param matrix 二维码
+ * @param writer 输出
+ */
+ public void render(final BitMatrix matrix, final Appendable writer) {
+ final int width = matrix.getWidth();
+ final int height = matrix.getHeight();
+
+ final Integer foreColor = config.getForeColor();
+ final AnsiElement foreground = foreColor == null ? null : ColorUtil.toAnsiColor(foreColor, true, false);
+ final Integer backColor = config.getBackColor();
+ final AnsiElement background = backColor == null ? null : ColorUtil.toAnsiColor(backColor, true, true);
+
+ try {
+ 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'
+ }
+ }
+ writer.append(AnsiEncoder.encode(foreground, background, rowBuilder)).append('\n');
+ }
+ } catch (final IOException e) {
+ throw new IORuntimeException(e);
+ }
+ }
+}
diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/BitMatrixRender.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/BitMatrixRender.java
new file mode 100644
index 000000000..efaa0fc9a
--- /dev/null
+++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/BitMatrixRender.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 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.render;
+
+import com.google.zxing.common.BitMatrix;
+
+import java.io.OutputStream;
+
+/**
+ * BitMatrix渲染接口
+ *
+ * @author Looly
+ * @since 6.0.0
+ */
+public interface BitMatrixRender {
+
+ /**
+ * 渲染
+ *
+ * @param matrix 二维码矩阵
+ * @param out 输出流
+ */
+ void render(BitMatrix matrix, OutputStream out);
+}
diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/ImageRender.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/ImageRender.java
new file mode 100644
index 000000000..f64788ff4
--- /dev/null
+++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/ImageRender.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 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.render;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.common.BitMatrix;
+import org.dromara.hutool.swing.img.Img;
+import org.dromara.hutool.swing.img.ImgUtil;
+import org.dromara.hutool.swing.qrcode.QrConfig;
+
+import java.awt.Image;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.io.OutputStream;
+
+/**
+ * 二维码图片渲染器
+ *
+ * @author Looly
+ * @since 6.0.0
+ */
+public class ImageRender implements BitMatrixRender {
+
+ private final QrConfig config;
+ private final String imageType;
+
+ /**
+ * 构造
+ *
+ * @param config 二维码配置
+ * @param imageType 图片类型
+ */
+ public ImageRender(final QrConfig config, final String imageType) {
+ this.config = config;
+ this.imageType = imageType;
+ }
+
+ @Override
+ public void render(final BitMatrix matrix, final OutputStream out) {
+ BufferedImage img = null;
+ try {
+ img = render(matrix);
+ ImgUtil.write(img, imageType, out);
+ } finally {
+ ImgUtil.flush(img);
+ }
+ }
+
+ /**
+ * 渲染
+ *
+ * @param matrix 二维码矩阵
+ * @return 图片
+ */
+ public BufferedImage render(final BitMatrix matrix) {
+ final BufferedImage image = getBufferedImage(matrix);
+
+ final Image logo = config.getImg();
+ if (null != logo && BarcodeFormat.QR_CODE == config.getFormat()) {
+ pressLogo(image, logo);
+ }
+ return image;
+ }
+
+ /**
+ * 获取图片
+ *
+ * @param matrix 二维码矩阵
+ * @return 图片
+ */
+ private BufferedImage getBufferedImage(final BitMatrix matrix) {
+ final BufferedImage image = new BufferedImage(
+ matrix.getWidth(),
+ matrix.getHeight(),
+ null == config.getBackColor() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
+
+ final int width = matrix.getWidth();
+ final int height = matrix.getHeight();
+ final Integer foreColor = config.getForeColor();
+ final Integer backColor = config.getBackColor();
+ 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 image 二维码图片
+ * @param logoImg logo图片
+ */
+ private void pressLogo(final BufferedImage image, final Image logoImg) {
+ // 只有二维码可以贴图
+ final int qrWidth = image.getWidth();
+ final int qrHeight = image.getHeight();
+ final int imgWidth;
+ final int imgHeight;
+ // 按照最短的边做比例缩放
+ if (qrWidth < qrHeight) {
+ imgWidth = qrWidth / config.getRatio();
+ imgHeight = logoImg.getHeight(null) * imgWidth / logoImg.getWidth(null);
+ } else {
+ imgHeight = qrHeight / config.getRatio();
+ imgWidth = logoImg.getWidth(null) * imgHeight / logoImg.getHeight(null);
+ }
+
+ // 原图片上直接绘制水印
+ Img.from(image).pressImage(//
+ Img.from(logoImg).round(config.getImgRound()).getImg(), // 圆角
+ new Rectangle(imgWidth, imgHeight), // 位置
+ 1//不透明
+ );
+ }
+}
diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/SVGRender.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/SVGRender.java
new file mode 100644
index 000000000..41cdcea75
--- /dev/null
+++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/SVGRender.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 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.render;
+
+import com.google.zxing.common.BitMatrix;
+import org.dromara.hutool.core.io.IORuntimeException;
+import org.dromara.hutool.core.text.StrUtil;
+import org.dromara.hutool.swing.img.ImgUtil;
+import org.dromara.hutool.swing.img.color.ColorUtil;
+import org.dromara.hutool.swing.qrcode.QrConfig;
+
+import java.awt.Color;
+import java.awt.Image;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+
+/**
+ * SVG渲染器
+ *
+ * @author Looly
+ * @since 6.0.0
+ */
+public class SVGRender implements BitMatrixRender {
+
+ private final QrConfig qrConfig;
+
+ /**
+ * 构造
+ *
+ * @param qrConfig 二维码配置
+ */
+ public SVGRender(final QrConfig qrConfig) {
+ this.qrConfig = qrConfig;
+ }
+
+ @Override
+ public void render(final BitMatrix matrix, final OutputStream out) {
+ render(matrix, new OutputStreamWriter(out, qrConfig.getCharset()));
+ }
+
+ /**
+ * 渲染SVG
+ *
+ * @param matrix 二维码
+ * @param writer 输出
+ */
+ public void render(final BitMatrix matrix, final Appendable writer) {
+ final Image logoImg = qrConfig.getImg();
+ final Integer foreColor = qrConfig.getForeColor();
+ final Integer backColor = qrConfig.getBackColor();
+ final int ratio = qrConfig.getRatio();
+
+ final int qrWidth = matrix.getWidth();
+ int qrHeight = matrix.getHeight();
+ final int moduleHeight = (qrHeight == 1) ? qrWidth / 2 : 1;
+
+ 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;
+
+ }
+
+ try {
+ writer.append("");
+ } catch (final IOException e) {
+ throw new IORuntimeException(e);
+ }
+ }
+}
diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/package-info.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/package-info.java
new file mode 100644
index 000000000..6da56e8e2
--- /dev/null
+++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/qrcode/render/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 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.
+ */
+
+/**
+ * 二维码渲染器
+ *
+ * @author Looly
+ * @since 6.0.0
+ */
+package org.dromara.hutool.swing.qrcode.render;