mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-08-18 20:38:02 +08:00
add BitMatrixRender
This commit is contained in:
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,19 +22,19 @@ import com.google.zxing.common.BitMatrix;
|
|||||||
import com.google.zxing.common.GlobalHistogramBinarizer;
|
import com.google.zxing.common.GlobalHistogramBinarizer;
|
||||||
import com.google.zxing.common.HybridBinarizer;
|
import com.google.zxing.common.HybridBinarizer;
|
||||||
import org.dromara.hutool.core.codec.binary.Base64;
|
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.FileNameUtil;
|
||||||
import org.dromara.hutool.core.io.file.FileUtil;
|
import org.dromara.hutool.core.io.file.FileUtil;
|
||||||
import org.dromara.hutool.core.net.url.UrlUtil;
|
import org.dromara.hutool.core.net.url.UrlUtil;
|
||||||
import org.dromara.hutool.core.util.ObjUtil;
|
import org.dromara.hutool.core.util.ObjUtil;
|
||||||
import org.dromara.hutool.swing.img.ImgUtil;
|
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;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.*;
|
||||||
import java.io.File;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,23 +140,11 @@ public class QrCodeUtil {
|
|||||||
*/
|
*/
|
||||||
public static File generate(final String content, final QrConfig config, final File targetFile) {
|
public static File generate(final String content, final QrConfig config, final File targetFile) {
|
||||||
final String extName = FileNameUtil.extName(targetFile);
|
final String extName = FileNameUtil.extName(targetFile);
|
||||||
switch (extName) {
|
try (final BufferedOutputStream outputStream = FileUtil.getOutputStream(targetFile)) {
|
||||||
case QR_TYPE_SVG:
|
generate(content, config, extName, outputStream);
|
||||||
FileUtil.writeUtf8String(generateAsSvg(content, config), targetFile);
|
} catch (final IOException e) {
|
||||||
break;
|
throw new RuntimeException(e);
|
||||||
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;
|
return targetFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,22 +171,18 @@ public class QrCodeUtil {
|
|||||||
* @since 4.1.2
|
* @since 4.1.2
|
||||||
*/
|
*/
|
||||||
public static void generate(final String content, final QrConfig config, final String imageType, final OutputStream out) {
|
public static void generate(final String content, final QrConfig config, final String imageType, final OutputStream out) {
|
||||||
|
final BitMatrixRender render;
|
||||||
switch (imageType) {
|
switch (imageType) {
|
||||||
case QR_TYPE_SVG:
|
case QR_TYPE_SVG:
|
||||||
IoUtil.writeUtf8(out, false, generateAsSvg(content, config));
|
render = new SVGRender(config);
|
||||||
break;
|
break;
|
||||||
case QR_TYPE_TXT:
|
case QR_TYPE_TXT:
|
||||||
IoUtil.writeUtf8(out, false, generateAsAsciiArt(content, config));
|
render = new AsciiArtRender(config);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
BufferedImage img = null;
|
render = new ImageRender(config, imageType);
|
||||||
try {
|
|
||||||
img = generate(content, config);
|
|
||||||
ImgUtil.write(img, imageType, out);
|
|
||||||
} finally {
|
|
||||||
ImgUtil.flush(img);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
render.render(encode(content, config), out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -223,7 +207,7 @@ public class QrCodeUtil {
|
|||||||
* @since 4.1.14
|
* @since 4.1.14
|
||||||
*/
|
*/
|
||||||
public static BufferedImage generate(final String content, final QrConfig config) {
|
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
|
// ------------------------------------------------------------------------------------------------------------------- encode
|
||||||
@@ -356,7 +340,9 @@ public class QrCodeUtil {
|
|||||||
* @return SVG矢量图(字符串)
|
* @return SVG矢量图(字符串)
|
||||||
*/
|
*/
|
||||||
public static String toSVG(final BitMatrix matrix, final QrConfig config) {
|
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字符画形式的二维码
|
* BitMatrix转ASCII Art字符画形式的二维码
|
||||||
*
|
*
|
||||||
* @param bitMatrix BitMatrix
|
* @param matrix BitMatrix
|
||||||
* @param qrConfig QR设置
|
* @param config QR设置
|
||||||
* @return ASCII Art字符画形式的二维码
|
* @return ASCII Art字符画形式的二维码
|
||||||
* @since 5.8.6
|
* @since 5.8.6
|
||||||
*/
|
*/
|
||||||
public static String toAsciiArt(final BitMatrix bitMatrix, final QrConfig qrConfig) {
|
public static String toAsciiArt(final BitMatrix matrix, final QrConfig config) {
|
||||||
return new QrAsciiArt(bitMatrix, qrConfig).toString();
|
final StringBuilder result = new StringBuilder();
|
||||||
|
new AsciiArtRender(config).render(matrix, result);
|
||||||
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// region ----- Private Methods
|
// region ----- Private Methods
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ public class QrConfig {
|
|||||||
*
|
*
|
||||||
* @return 前景色
|
* @return 前景色
|
||||||
*/
|
*/
|
||||||
public int getForeColor() {
|
public Integer getForeColor() {
|
||||||
return foreColor;
|
return foreColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +207,7 @@ public class QrConfig {
|
|||||||
*
|
*
|
||||||
* @return 背景色
|
* @return 背景色
|
||||||
*/
|
*/
|
||||||
public int getBackColor() {
|
public Integer getBackColor() {
|
||||||
return backColor;
|
return backColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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//不透明
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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//不透明
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
Reference in New Issue
Block a user