diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrAsciiArt.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrAsciiArt.java new file mode 100644 index 000000000..59d358fd1 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrAsciiArt.java @@ -0,0 +1,57 @@ +package cn.hutool.extra.qrcode; + +import cn.hutool.core.lang.ansi.AnsiElement; +import cn.hutool.core.lang.ansi.AnsiEncoder; +import cn.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(BitMatrix matrix, QrConfig qrConfig) { + this.matrix = matrix; + this.qrConfig = qrConfig; + } + + @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); + + StringBuilder builder = new StringBuilder(); + for (int i = 0; i <= height; i += 2) { + StringBuilder rowBuilder = new StringBuilder(); + for (int j = 0; j < width; j++) { + boolean tp = matrix.get(i, j); + 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-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java index a470f035d..45c604dcb 100755 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java @@ -1,31 +1,20 @@ package cn.hutool.extra.qrcode; import cn.hutool.core.codec.Base64; -import cn.hutool.swing.img.Img; +import cn.hutool.core.util.ObjUtil; import cn.hutool.swing.img.ImgUtil; -import cn.hutool.core.util.CharsetUtil; import com.google.zxing.BarcodeFormat; -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.MultiFormatWriter; -import com.google.zxing.NotFoundException; -import com.google.zxing.Result; -import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; import com.google.zxing.common.GlobalHistogramBinarizer; import com.google.zxing.common.HybridBinarizer; import java.awt.Image; -import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; import java.io.OutputStream; -import java.util.HashMap; import java.util.Map; /** @@ -40,44 +29,33 @@ import java.util.Map; */ public class QrCodeUtil { + public static final String QR_TYPE_SVG = "svg";// SVG矢量图格式 + public static final String QR_TYPE_TXT = "txt";// Ascii Art字符画文本 + /** * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 * * @param content 内容 * @param qrConfig 二维码配置,包括宽度、高度、边距、颜色等 - * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} + * @param targetType 类型(图片扩展名),见{@link #QR_TYPE_SVG}、 {@link #QR_TYPE_TXT}、{@link ImgUtil} * @param logoBase64 logo 图片的 base64 编码 * @return 图片 Base64 编码字符串 */ - public static String generateAsBase64(final String content, final QrConfig qrConfig, final String imageType, final String logoBase64) { - return generateAsBase64(content, qrConfig, imageType, Base64.decode(logoBase64)); + public static String generateAsBase64(String content, QrConfig qrConfig, String targetType, String logoBase64) { + return generateAsBase64DataUri(content, qrConfig, targetType, Base64.decode(logoBase64)); } /** * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 * - * @param content 内容 - * @param qrConfig 二维码配置,包括宽度、高度、边距、颜色等 - * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} - * @param logo logo 图片的byte[] + * @param content 内容 + * @param qrConfig 二维码配置,包括宽度、高度、边距、颜色等 + * @param targetType 类型(图片扩展名),见{@link #QR_TYPE_SVG}、 {@link #QR_TYPE_TXT}、{@link ImgUtil} + * @param logo logo 图片的byte[] * @return 图片 Base64 编码字符串 */ - public static String generateAsBase64(final String content, final QrConfig qrConfig, final String imageType, final byte[] logo) { - return generateAsBase64(content, qrConfig, imageType, ImgUtil.toImage(logo)); - } - - /** - * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 - * - * @param content 内容 - * @param qrConfig 二维码配置,包括宽度、高度、边距、颜色等 - * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} - * @param logo logo 图片的byte[] - * @return 图片 Base64 编码字符串 - */ - public static String generateAsBase64(final String content, final QrConfig qrConfig, final String imageType, final Image logo) { - qrConfig.setImg(logo); - return generateAsBase64(content, qrConfig, imageType); + public static String generateAsBase64DataUri(String content, QrConfig qrConfig, String targetType, byte[] logo) { + return generateAsBase64DataUri(content, qrConfig.setImg(ImgUtil.toImage(logo)), targetType); } /** @@ -92,7 +70,7 @@ public class QrCodeUtil { * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} * @return 图片 Base64 编码字符串 */ - public static String generateAsBase64(final String content, final QrConfig qrConfig, final String imageType) { + public static String generateAsBase64DataUri(final String content, final QrConfig qrConfig, final String imageType) { final BufferedImage img = generate(content, qrConfig); return ImgUtil.toBase64DataUri(img, imageType); } @@ -193,32 +171,7 @@ public class QrCodeUtil { * @return 二维码图片(黑白) */ public static BufferedImage generate(final String content, final int width, final int height) { - return generate(content, new QrConfig(width, height)); - } - - /** - * 生成二维码或条形码图片 - * - * @param content 文本内容 - * @param format 格式,可选二维码或者条形码 - * @param width 宽度 - * @param height 高度 - * @return 二维码图片(黑白) - */ - public static BufferedImage generate(final String content, final BarcodeFormat format, final int width, final int height) { - return generate(content, format, new QrConfig(width, height)); - } - - /** - * 生成二维码图片 - * - * @param content 文本内容 - * @param config 二维码配置,包括宽度、高度、边距、颜色等 - * @return 二维码图片(黑白) - * @since 4.1.2 - */ - public static BufferedImage generate(final String content, final QrConfig config) { - return generate(content, BarcodeFormat.QR_CODE, config); + return generate(content, QrConfig.of(width, height)); } /** @@ -226,102 +179,26 @@ public class QrCodeUtil { * 只有二维码时QrConfig中的图片才有效 * * @param content 文本内容 - * @param format 格式,可选二维码、条形码等 * @param config 二维码配置,包括宽度、高度、边距、颜色等 * @return 二维码图片(黑白) * @since 4.1.14 */ - public static BufferedImage generate(final String content, final BarcodeFormat format, final QrConfig config) { - final BitMatrix bitMatrix = encode(content, format, config); - final BufferedImage image = toImage(bitMatrix, config.foreColor, config.backColor); - final Image logoImg = config.img; - if (null != logoImg && BarcodeFormat.QR_CODE == format) { - // 只有二维码可以贴图 - final int qrWidth = image.getWidth(); - final int qrHeight = image.getHeight(); - final int width; - final int height; - // 按照最短的边做比例缩放 - if (qrWidth < qrHeight) { - width = qrWidth / config.ratio; - height = logoImg.getHeight(null) * width / logoImg.getWidth(null); - } else { - height = qrHeight / config.ratio; - width = logoImg.getWidth(null) * height / logoImg.getHeight(null); - } - - Img.from(image).pressImage(// - Img.from(logoImg).round(0.3).getImg(), // 圆角 - new Rectangle(width, height), // - 1// - ); - } - return image; + public static BufferedImage generate(final String content, final QrConfig config) { + return new QrImage(content, ObjUtil.defaultIfNull(config, QrConfig::new)); } // ------------------------------------------------------------------------------------------------------------------- encode - /** - * 将文本内容编码为二维码 - * - * @param content 文本内容 - * @param width 宽度 - * @param height 高度 - * @return {@link BitMatrix} - */ - public static BitMatrix encode(final String content, final int width, final int height) { - return encode(content, BarcodeFormat.QR_CODE, width, height); - } - - /** - * 将文本内容编码为二维码 - * - * @param content 文本内容 - * @param config 二维码配置,包括宽度、高度、边距、颜色等 - * @return {@link BitMatrix} - * @since 4.1.2 - */ - public static BitMatrix encode(final String content, final QrConfig config) { - return encode(content, BarcodeFormat.QR_CODE, config); - } - /** * 将文本内容编码为条形码或二维码 * * @param content 文本内容 - * @param format 格式枚举 - * @param width 宽度 - * @param height 高度 - * @return {@link BitMatrix} - */ - public static BitMatrix encode(final String content, final BarcodeFormat format, final int width, final int height) { - return encode(content, format, new QrConfig(width, height)); - } - - /** - * 将文本内容编码为条形码或二维码 - * - * @param content 文本内容 - * @param format 格式枚举 - * @param config 二维码配置,包括宽度、高度、边距、颜色等 + * @param config 二维码配置,包括宽度、高度、边距、颜色、格式等 * @return {@link BitMatrix} * @since 4.1.2 */ - public static BitMatrix encode(final String content, final BarcodeFormat format, QrConfig config) { - final MultiFormatWriter multiFormatWriter = new MultiFormatWriter(); - if (null == config) { - // 默认配置 - config = new QrConfig(); - } - - final BitMatrix bitMatrix; - try { - bitMatrix = multiFormatWriter.encode(content, format, config.width, config.height, config.toHints(format)); - } catch (final WriterException e) { - throw new QrCodeException(e); - } - - return bitMatrix; + public static BitMatrix encode(final CharSequence content, final QrConfig config) { + return QrEncoder.of(config).encode(content); } // ------------------------------------------------------------------------------------------------------------------- decode @@ -368,7 +245,7 @@ public class QrCodeUtil { * @since 4.3.1 */ public static String decode(final Image image, final boolean isTryHarder, final boolean isPureBarcode) { - return decode(image, buildHints(isTryHarder, isPureBarcode)); + return QrDecoder.of(isTryHarder, isPureBarcode).decode(image); } /** @@ -382,17 +259,7 @@ public class QrCodeUtil { * @since 5.7.12 */ public static String decode(final Image image, final Map hints) { - final MultiFormatReader formatReader = new MultiFormatReader(); - formatReader.setHints(hints); - - final LuminanceSource source = new BufferedImageLuminanceSource(ImgUtil.toBufferedImage(image)); - - Result result = _decode(formatReader, new HybridBinarizer(source)); - if (null == result) { - result = _decode(formatReader, new GlobalHistogramBinarizer(source)); - } - - return null != result ? result.getText() : null; + return QrDecoder.of(hints).decode(image); } /** @@ -421,39 +288,46 @@ public class QrCodeUtil { } /** - * 创建解码选项 - * - * @param isTryHarder 是否优化精度 - * @param isPureBarcode 是否使用复杂模式,扫描带logo的二维码设为true - * @return 选项Map + * @param content 内容 + * @param qrConfig 二维码配置,包括宽度、高度、边距、颜色等 + * @return SVG矢量图(字符串) + * @since 5.8.6 */ - private static Map buildHints(final boolean isTryHarder, final boolean isPureBarcode) { - final HashMap hints = new HashMap<>(); - 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; + public static String generateAsSvg(String content, QrConfig qrConfig) { + return toSVG(encode(content, qrConfig), qrConfig); } /** - * 解码多种类型的码,包括二维码和条形码 + * BitMatrix转SVG(字符串) * - * @param formatReader {@link MultiFormatReader} - * @param binarizer {@link Binarizer} - * @return {@link Result} + * @param matrix BitMatrix + * @param config {@link QrConfig} + * @return SVG矢量图(字符串) */ - private static Result _decode(final MultiFormatReader formatReader, final Binarizer binarizer) { - try { - return formatReader.decodeWithState(new BinaryBitmap(binarizer)); - } catch (final NotFoundException e) { - return null; - } + public static String toSVG(BitMatrix matrix, 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(String content, QrConfig qrConfig) { + return toAsciiArt(encode(content, qrConfig), qrConfig); + } + + /** + * BitMatrix转ASCII Art字符画形式的二维码 + * + * @param bitMatrix BitMatrix + * @return ASCII Art字符画形式的二维码 + * @since 5.8.6 + */ + public static String toAsciiArt(BitMatrix bitMatrix, QrConfig qrConfig) { + return new QrAsciiArt(bitMatrix, qrConfig).toString(); } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java index 9380cc8c2..1dec0bcc0 100755 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java @@ -25,33 +25,59 @@ 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表示透明 */ + /** + * 背景色,默认白色,null表示透明 + */ protected Integer backColor = WHITE; - /** 边距0~4 */ + /** + * 边距0~4 + */ protected Integer margin = 2; - /** 设置二维码中的信息量,可设置0-40的整数 */ + /** + * 设置二维码中的信息量,可设置0-40的整数 + */ protected Integer qrVersion; - /** 纠错级别 */ + /** + * 纠错级别 + */ protected ErrorCorrectionLevel errorCorrection = ErrorCorrectionLevel.M; - /** 编码 */ + /** + * 编码 + */ protected Charset charset = CharsetUtil.UTF_8; - /** 二维码中的Logo */ + /** + * 二维码中的Logo + */ protected Image img; - /** 二维码中的Logo缩放的比例系数,如5表示长宽最小值的1/5 */ + /** + * 二维码中的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 */ @@ -59,6 +85,18 @@ public class QrConfig { 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 */ @@ -69,8 +107,8 @@ public class QrConfig { /** * 构造 * - * @param width 宽 - * @param height 长 + * @param width 宽 + * @param height 高 */ public QrConfig(final int width, final int height) { this.width = width; @@ -134,7 +172,7 @@ public class QrConfig { * @since 5.1.1 */ public QrConfig setForeColor(final Color foreColor) { - if(null == foreColor){ + if (null == foreColor) { this.foreColor = null; } else { this.foreColor = foreColor.getRGB(); @@ -159,7 +197,7 @@ public class QrConfig { * @since 5.1.1 */ public QrConfig setBackColor(final Color backColor) { - if(null == backColor){ + if (null == backColor) { this.backColor = null; } else { this.backColor = backColor.getRGB(); @@ -318,22 +356,33 @@ public class QrConfig { return this; } + /** + * 获取码格式 + * + * @return 码格式,默认为二维码 + */ + public BarcodeFormat getFormat() { + return format; + } + + /** + * 设置码格式,默认二维码 + * + * @param format 码格式 + * @return this + */ + public QrConfig setFormat(BarcodeFormat format) { + this.format = format; + return this; + } + + /** * 转换为Zxing的二维码配置 * * @return 配置 */ public HashMap toHints() { - return toHints(BarcodeFormat.QR_CODE); - } - - /** - * 转换为Zxing的二维码配置 - * - * @param format 格式,根据格式不同,{@link #errorCorrection}的值类型有所不同 - * @return 配置 - */ - public HashMap toHints(final BarcodeFormat format) { // 配置 final HashMap hints = new HashMap<>(); if (null != this.charset) { @@ -341,7 +390,7 @@ public class QrConfig { } if (null != this.errorCorrection) { final Object value; - if(BarcodeFormat.AZTEC == format || BarcodeFormat.PDF_417 == format){ + if (BarcodeFormat.AZTEC == format || BarcodeFormat.PDF_417 == format) { // issue#I4FE3U@Gitee value = this.errorCorrection.getBits(); } else { @@ -354,7 +403,7 @@ public class QrConfig { if (null != this.margin) { hints.put(EncodeHintType.MARGIN, this.margin); } - if (null != this.qrVersion){ + if (null != this.qrVersion) { hints.put(EncodeHintType.QR_VERSION, this.qrVersion); } return hints; diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrDecoder.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrDecoder.java new file mode 100644 index 000000000..ddcfed6a6 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrDecoder.java @@ -0,0 +1,111 @@ +package cn.hutool.extra.qrcode; + +import cn.hutool.core.codec.Decoder; +import cn.hutool.core.util.CharsetUtil; +import cn.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 { + + private final Map 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 hints) { + return new QrDecoder(hints); + } + + /** + * 构造 + * + * @param hints 自定义扫码配置,包括算法、编码、复杂模式等 + */ + public QrDecoder(Map hints) { + this.hints = hints; + } + + @Override + public String decode(Image image) { + final MultiFormatReader formatReader = new MultiFormatReader(); + formatReader.setHints(hints); + + final LuminanceSource source = new BufferedImageLuminanceSource(ImgUtil.toBufferedImage(image)); + + 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 buildHints(final boolean isTryHarder, final boolean isPureBarcode) { + final HashMap 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; + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrEncoder.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrEncoder.java new file mode 100644 index 000000000..fae462310 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrEncoder.java @@ -0,0 +1,55 @@ +package cn.hutool.extra.qrcode; + +import cn.hutool.core.codec.Encoder; +import cn.hutool.core.text.StrUtil; +import cn.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 { + + /** + * 创建QrEncoder + * + * @param config {@link QrConfig} + * @return QrEncoder + */ + public static QrEncoder of(QrConfig config) { + return new QrEncoder(config); + } + + private final QrConfig config; + + /** + * 构造 + * + * @param config {@link QrConfig} + */ + public QrEncoder(QrConfig config) { + this.config = ObjUtil.defaultIfNull(config, QrConfig::of); + } + + @Override + public BitMatrix encode(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; + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrImage.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrImage.java new file mode 100644 index 000000000..b3de1d253 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrImage.java @@ -0,0 +1,84 @@ +package cn.hutool.extra.qrcode; + +import cn.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(0.3).getImg(), // 圆角 + new Rectangle(imgWidth, imgHeight), // 位置 + 1//不透明 + ); + } + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrSVG.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrSVG.java index 8a2001f81..8573f291a 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrSVG.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrSVG.java @@ -17,6 +17,11 @@ public class QrSVG { private final BitMatrix matrix; private final QrConfig qrConfig; + /** + * 构造 + * @param matrix {@link BitMatrix} + * @param qrConfig {@link QrConfig} + */ public QrSVG(BitMatrix matrix, QrConfig qrConfig) { this.matrix = matrix; this.qrConfig = qrConfig; diff --git a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java index b7da768a7..2637bfa52 100755 --- a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java @@ -2,7 +2,9 @@ package cn.hutool.extra.qrcode; import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Console; +import cn.hutool.core.text.StrUtil; import cn.hutool.swing.img.ImgUtil; import com.google.zxing.BarcodeFormat; import com.google.zxing.datamatrix.encoder.SymbolShapeHint; @@ -14,6 +16,7 @@ import org.junit.Test; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.File; +import java.nio.charset.StandardCharsets; /** * 二维码工具类单元测试 @@ -31,7 +34,7 @@ public class QrCodeUtilTest { @Test @Ignore public void generateCustomTest() { - final QrConfig config = new QrConfig(); + final QrConfig config = QrConfig.of(); config.setMargin(0); config.setForeColor(Color.CYAN); // 背景色透明 @@ -44,7 +47,7 @@ public class QrCodeUtilTest { @Test @Ignore public void generateWithLogoTest() { - final String icon = FileUtil.isWindows() ? "d:/test/pic/face.jpg" : "~/Desktop/hutool/pic/face.jpg"; + final String icon = FileUtil.isWindows() ? "d:/test/pic/logo.jpg" : "~/Desktop/hutool/pic/logo.jpg"; final String targetPath = FileUtil.isWindows() ? "d:/test/qrcodeWithLogo.jpg" : "~/Desktop/hutool/qrcodeWithLogo.jpg"; QrCodeUtil.generate(// "https://hutool.cn/", // @@ -68,17 +71,22 @@ public class QrCodeUtilTest { } @Test - public void generateAsBase64Test() { - final String base64 = QrCodeUtil.generateAsBase64("https://hutool.cn/", new QrConfig(400, 400), "png"); + public void generateAsBase64AndDecodeTest() { + final String url = "https://hutool.cn/"; + String base64 = QrCodeUtil.generateAsBase64DataUri(url, new QrConfig(400, 400), "png"); Assert.assertNotNull(base64); + + base64 = StrUtil.removePrefix(base64, "data:image/png;base64,"); + final String decode = QrCodeUtil.decode(IoUtil.toStream(Base64.decode(base64))); + Assert.assertEquals(url, decode); } @Test @Ignore public void generateAsBase64Test2() { final byte[] bytes = FileUtil.readBytes(new File("d:/test/qr.png")); - final String encode = Base64.encode(bytes); - final String base641 = QrCodeUtil.generateAsBase64("https://hutool.cn/", new QrConfig(400, 400), "png", encode); + final String base641 = QrCodeUtil.generateAsBase64DataUri("https://hutool.cn/", + new QrConfig(400, 400), "png", bytes); Assert.assertNotNull(base641); } @@ -91,7 +99,7 @@ public class QrCodeUtilTest { @Test public void pdf417Test() { - final BufferedImage image = QrCodeUtil.generate("content111", BarcodeFormat.PDF_417, QrConfig.of()); + final BufferedImage image = QrCodeUtil.generate("content111", QrConfig.of().setFormat(BarcodeFormat.PDF_417)); Assert.assertNotNull(image); } @@ -99,11 +107,50 @@ public class QrCodeUtilTest { public void generateDataMatrixTest() { final QrConfig qrConfig = QrConfig.of(); qrConfig.setShapeHint(SymbolShapeHint.FORCE_RECTANGLE); - final BufferedImage image = QrCodeUtil.generate("content111", BarcodeFormat.DATA_MATRIX, qrConfig); + final BufferedImage image = QrCodeUtil.generate("content111", qrConfig.setFormat(BarcodeFormat.DATA_MATRIX)); Assert.assertNotNull(image); final QrConfig config = QrConfig.of(); config.setShapeHint(SymbolShapeHint.FORCE_SQUARE); - final BufferedImage imageSquare = QrCodeUtil.generate("content111", BarcodeFormat.DATA_MATRIX, qrConfig); + final BufferedImage imageSquare = QrCodeUtil.generate("content111", qrConfig.setFormat(BarcodeFormat.DATA_MATRIX)); Assert.assertNotNull(imageSquare); } + + @Test + @Ignore + public void generateSvgTest() { + final QrConfig qrConfig = QrConfig.of() + .setImg("d:/test/pic/logo.jpg") + .setForeColor(Color.blue) + .setBackColor(Color.pink) + .setRatio(8) + .setErrorCorrection(ErrorCorrectionLevel.M) + .setMargin(1); + final String svg = QrCodeUtil.generateAsSvg("https://hutool.cn/", qrConfig); + Assert.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); + Assert.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); + Assert.assertNotNull(asciiArt); + //Console.log(asciiArt); + } } diff --git a/hutool-swing/src/main/java/cn/hutool/swing/img/color/Ansi8bitMapping.java b/hutool-swing/src/main/java/cn/hutool/swing/img/color/Ansi8bitMapping.java index 49c340cb6..e380138ff 100644 --- a/hutool-swing/src/main/java/cn/hutool/swing/img/color/Ansi8bitMapping.java +++ b/hutool-swing/src/main/java/cn/hutool/swing/img/color/Ansi8bitMapping.java @@ -10,8 +10,6 @@ import java.util.LinkedHashMap; * @author Tom Xin */ public class Ansi8bitMapping extends AnsiLabMapping { - public static final Ansi8bitMapping INSTANCE = new Ansi8bitMapping(); - private static final int[] ANSI_8BIT_COLOR_CODE_LOOKUP = new int[] { 0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xc0c0c0, 0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff, 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, @@ -40,6 +38,8 @@ public class Ansi8bitMapping extends AnsiLabMapping { 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee }; + public static final Ansi8bitMapping INSTANCE = new Ansi8bitMapping(); + public Ansi8bitMapping() { ansiLabMap = new LinkedHashMap<>(256, 1); for (int i = 0; i < ANSI_8BIT_COLOR_CODE_LOOKUP.length; i++) { diff --git a/hutool-swing/src/main/java/cn/hutool/swing/img/color/ColorUtil.java b/hutool-swing/src/main/java/cn/hutool/swing/img/color/ColorUtil.java index e14825c6d..bbfe5f9ef 100644 --- a/hutool-swing/src/main/java/cn/hutool/swing/img/color/ColorUtil.java +++ b/hutool-swing/src/main/java/cn/hutool/swing/img/color/ColorUtil.java @@ -197,6 +197,18 @@ public class ColorUtil { return new Color(random.nextInt(RGB_COLOR_BOUND), random.nextInt(RGB_COLOR_BOUND), random.nextInt(RGB_COLOR_BOUND)); } + /** + * AWT的{@link Color}颜色转换为ANSI颜色,由于取最接近颜色,故可能有色差 + * + * @param rgb RGB颜色 + * @param is8Bit 是否8bit的ANSI颜色 + * @param isBackground 是否背景色 + * @return ANSI颜色 + */ + public static AnsiElement toAnsiColor(int rgb, boolean is8Bit, boolean isBackground) { + return toAnsiColor(getColor(rgb), is8Bit, isBackground); + } + /** * AWT的{@link Color}颜色转换为ANSI颜色,由于取最接近颜色,故可能有色差 *