add BitMatrixRender

This commit is contained in:
Looly 2025-01-05 23:10:48 +08:00
parent dfacb2c024
commit 89dd871af7
10 changed files with 442 additions and 320 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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("<svg width=\"").append(String.valueOf(qrWidth)).append("\" height=\"").append(String.valueOf(qrHeight)).append("\" \n");
if (backColor != null) {
final Color back = new Color(backColor, true);
writer.append("style=\"background-color:").append(ColorUtil.toCssRgba(back)).append("\"\n");
}
writer.append("viewBox=\"0 0 ").append(String.valueOf(qrWidth)).append(" ").append(String.valueOf(qrHeight)).append("\" \n");
writer.append("xmlns=\"http://www.w3.org/2000/svg\" \n");
writer.append("xmlns:xlink=\"http://www.w3.org/1999/xlink\" >\n");
writer.append("<path d=\"");
// 数据
for (int y = 0; y < qrHeight; y++) {
for (int x = 0; x < qrWidth; x++) {
if (matrix.get(x, y)) {
writer.append(" M").append(String.valueOf(x)).append(",").append(String.valueOf(y)).append("h1v").append(String.valueOf(moduleHeight)).append("h-1z");
}
}
}
writer.append("\" ");
if (foreColor != null) {
final Color fore = new Color(foreColor, true);
writer.append("stroke=\"").append(ColorUtil.toCssRgba(fore)).append("\"");
}
writer.append(" /> \n");
if (StrUtil.isNotBlank(logoBase64)) {
writer.append("<image xlink:href=\"").append(logoBase64)
.append("\" height=\"").append(String.valueOf(logoHeight)).append("\" width=\"").append(String.valueOf(logoWidth))
.append("\" y=\"").append(String.valueOf(logoY)).append("\" x=\"").append(String.valueOf(logoX)).append("\" />\n");
}
writer.append("</svg>");
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
}

View File

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