增加QrXXX

This commit is contained in:
Looly 2022-09-15 19:12:50 +08:00
parent eba3332eaf
commit 37e971c4bc
10 changed files with 514 additions and 220 deletions

View File

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

View File

@ -1,31 +1,20 @@
package cn.hutool.extra.qrcode; package cn.hutool.extra.qrcode;
import cn.hutool.core.codec.Base64; 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.swing.img.ImgUtil;
import cn.hutool.core.util.CharsetUtil;
import com.google.zxing.BarcodeFormat; import com.google.zxing.BarcodeFormat;
import com.google.zxing.Binarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType; 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.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 java.awt.Image; import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
@ -40,17 +29,20 @@ import java.util.Map;
*/ */
public class QrCodeUtil { 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 形式表示 * 生成代 logo 图片的 Base64 编码格式的二维码 String 形式表示
* *
* @param content 内容 * @param content 内容
* @param qrConfig 二维码配置包括宽度高度边距颜色等 * @param qrConfig 二维码配置包括宽度高度边距颜色等
* @param imageType 图片类型图片扩展名{@link ImgUtil} * @param targetType 类型图片扩展名{@link #QR_TYPE_SVG} {@link #QR_TYPE_TXT}{@link ImgUtil}
* @param logoBase64 logo 图片的 base64 编码 * @param logoBase64 logo 图片的 base64 编码
* @return 图片 Base64 编码字符串 * @return 图片 Base64 编码字符串
*/ */
public static String generateAsBase64(final String content, final QrConfig qrConfig, final String imageType, final String logoBase64) { public static String generateAsBase64(String content, QrConfig qrConfig, String targetType, String logoBase64) {
return generateAsBase64(content, qrConfig, imageType, Base64.decode(logoBase64)); return generateAsBase64DataUri(content, qrConfig, targetType, Base64.decode(logoBase64));
} }
/** /**
@ -58,26 +50,12 @@ public class QrCodeUtil {
* *
* @param content 内容 * @param content 内容
* @param qrConfig 二维码配置包括宽度高度边距颜色等 * @param qrConfig 二维码配置包括宽度高度边距颜色等
* @param imageType 图片类型图片扩展名{@link ImgUtil} * @param targetType 类型图片扩展名{@link #QR_TYPE_SVG} {@link #QR_TYPE_TXT}{@link ImgUtil}
* @param logo logo 图片的byte[] * @param logo logo 图片的byte[]
* @return 图片 Base64 编码字符串 * @return 图片 Base64 编码字符串
*/ */
public static String generateAsBase64(final String content, final QrConfig qrConfig, final String imageType, final byte[] logo) { public static String generateAsBase64DataUri(String content, QrConfig qrConfig, String targetType, byte[] logo) {
return generateAsBase64(content, qrConfig, imageType, ImgUtil.toImage(logo)); return generateAsBase64DataUri(content, qrConfig.setImg(ImgUtil.toImage(logo)), targetType);
}
/**
* 生成代 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);
} }
/** /**
@ -92,7 +70,7 @@ public class QrCodeUtil {
* @param imageType 图片类型图片扩展名{@link ImgUtil} * @param imageType 图片类型图片扩展名{@link ImgUtil}
* @return 图片 Base64 编码字符串 * @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); final BufferedImage img = generate(content, qrConfig);
return ImgUtil.toBase64DataUri(img, imageType); return ImgUtil.toBase64DataUri(img, imageType);
} }
@ -193,32 +171,7 @@ public class QrCodeUtil {
* @return 二维码图片黑白 * @return 二维码图片黑白
*/ */
public static BufferedImage generate(final String content, final int width, final int height) { public static BufferedImage generate(final String content, final int width, final int height) {
return generate(content, new QrConfig(width, height)); return generate(content, QrConfig.of(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);
} }
/** /**
@ -226,102 +179,26 @@ public class QrCodeUtil {
* 只有二维码时QrConfig中的图片才有效 * 只有二维码时QrConfig中的图片才有效
* *
* @param content 文本内容 * @param content 文本内容
* @param format 格式可选二维码条形码等
* @param config 二维码配置包括宽度高度边距颜色等 * @param config 二维码配置包括宽度高度边距颜色等
* @return 二维码图片黑白 * @return 二维码图片黑白
* @since 4.1.14 * @since 4.1.14
*/ */
public static BufferedImage generate(final String content, final BarcodeFormat format, final QrConfig config) { public static BufferedImage generate(final String content, final QrConfig config) {
final BitMatrix bitMatrix = encode(content, format, config); return new QrImage(content, ObjUtil.defaultIfNull(config, QrConfig::new));
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;
} }
// ------------------------------------------------------------------------------------------------------------------- encode // ------------------------------------------------------------------------------------------------------------------- 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 content 文本内容
* @param format 格式枚举 * @param config 二维码配置包括宽度高度边距颜色格式等
* @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 二维码配置包括宽度高度边距颜色等
* @return {@link BitMatrix} * @return {@link BitMatrix}
* @since 4.1.2 * @since 4.1.2
*/ */
public static BitMatrix encode(final String content, final BarcodeFormat format, QrConfig config) { public static BitMatrix encode(final CharSequence content, final QrConfig config) {
final MultiFormatWriter multiFormatWriter = new MultiFormatWriter(); return QrEncoder.of(config).encode(content);
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;
} }
// ------------------------------------------------------------------------------------------------------------------- decode // ------------------------------------------------------------------------------------------------------------------- decode
@ -368,7 +245,7 @@ public class QrCodeUtil {
* @since 4.3.1 * @since 4.3.1
*/ */
public static String decode(final Image image, final boolean isTryHarder, final boolean isPureBarcode) { 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 * @since 5.7.12
*/ */
public static String decode(final Image image, final Map<DecodeHintType, Object> hints) { public static String decode(final Image image, final Map<DecodeHintType, Object> hints) {
final MultiFormatReader formatReader = new MultiFormatReader(); return QrDecoder.of(hints).decode(image);
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;
} }
/** /**
@ -421,39 +288,46 @@ public class QrCodeUtil {
} }
/** /**
* 创建解码选项 * @param content 内容
* * @param qrConfig 二维码配置包括宽度高度边距颜色等
* @param isTryHarder 是否优化精度 * @return SVG矢量图字符串
* @param isPureBarcode 是否使用复杂模式扫描带logo的二维码设为true * @since 5.8.6
* @return 选项Map
*/ */
private static Map<DecodeHintType, Object> buildHints(final boolean isTryHarder, final boolean isPureBarcode) { public static String generateAsSvg(String content, QrConfig qrConfig) {
final HashMap<DecodeHintType, Object> hints = new HashMap<>(); return toSVG(encode(content, qrConfig), qrConfig);
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;
} }
/** /**
* 解码多种类型的码包括二维码和条形码 * BitMatrix转SVG(字符串)
* *
* @param formatReader {@link MultiFormatReader} * @param matrix BitMatrix
* @param binarizer {@link Binarizer} * @param config {@link QrConfig}
* @return {@link Result} * @return SVG矢量图字符串
*/ */
private static Result _decode(final MultiFormatReader formatReader, final Binarizer binarizer) { public static String toSVG(BitMatrix matrix, QrConfig config) {
try { return new QrSVG(matrix, config).toString();
return formatReader.decodeWithState(new BinaryBitmap(binarizer)); }
} catch (final NotFoundException e) {
return null; /**
} * 生成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();
} }
} }

View File

@ -25,33 +25,59 @@ public class QrConfig {
private static final int BLACK = 0xFF000000; private static final int BLACK = 0xFF000000;
private static final int WHITE = 0xFFFFFFFF; private static final int WHITE = 0xFFFFFFFF;
/** 宽度 */ /**
* 宽度
*/
protected int width; protected int width;
/** 高度 */ /**
* 高度
*/
protected int height; protected int height;
/** 前景色(二维码颜色) */ /**
* 前景色二维码颜色
*/
protected Integer foreColor = BLACK; protected Integer foreColor = BLACK;
/** 背景色默认白色null表示透明 */ /**
* 背景色默认白色null表示透明
*/
protected Integer backColor = WHITE; protected Integer backColor = WHITE;
/** 边距0~4 */ /**
* 边距0~4
*/
protected Integer margin = 2; protected Integer margin = 2;
/** 设置二维码中的信息量可设置0-40的整数 */ /**
* 设置二维码中的信息量可设置0-40的整数
*/
protected Integer qrVersion; protected Integer qrVersion;
/** 纠错级别 */ /**
* 纠错级别
*/
protected ErrorCorrectionLevel errorCorrection = ErrorCorrectionLevel.M; protected ErrorCorrectionLevel errorCorrection = ErrorCorrectionLevel.M;
/** 编码 */ /**
* 编码
*/
protected Charset charset = CharsetUtil.UTF_8; protected Charset charset = CharsetUtil.UTF_8;
/** 二维码中的Logo */ /**
* 二维码中的Logo
*/
protected Image img; protected Image img;
/** 二维码中的Logo缩放的比例系数如5表示长宽最小值的1/5 */ /**
* 二维码中的Logo缩放的比例系数如5表示长宽最小值的1/5
*/
protected int ratio = 6; protected int ratio = 6;
/** /**
* DATA_MATRIX的符号形状 * DATA_MATRIX的符号形状
*/ */
protected SymbolShapeHint shapeHint = SymbolShapeHint.FORCE_NONE; protected SymbolShapeHint shapeHint = SymbolShapeHint.FORCE_NONE;
/**
* 生成码的格式默认为二维码
*/
protected BarcodeFormat format = BarcodeFormat.QR_CODE;
/** /**
* 创建QrConfig * 创建QrConfig
*
* @return QrConfig * @return QrConfig
* @since 4.1.14 * @since 4.1.14
*/ */
@ -59,6 +85,18 @@ public class QrConfig {
return new 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 * 构造默认长宽为300
*/ */
@ -70,7 +108,7 @@ public class QrConfig {
* 构造 * 构造
* *
* @param width * @param width
* @param height * @param height
*/ */
public QrConfig(final int width, final int height) { public QrConfig(final int width, final int height) {
this.width = width; this.width = width;
@ -318,22 +356,33 @@ public class QrConfig {
return this; return this;
} }
/**
* 获取码格式
*
* @return 码格式默认为二维码
*/
public BarcodeFormat getFormat() {
return format;
}
/**
* 设置码格式默认二维码
*
* @param format 码格式
* @return this
*/
public QrConfig setFormat(BarcodeFormat format) {
this.format = format;
return this;
}
/** /**
* 转换为Zxing的二维码配置 * 转换为Zxing的二维码配置
* *
* @return 配置 * @return 配置
*/ */
public HashMap<EncodeHintType, Object> toHints() { public HashMap<EncodeHintType, Object> toHints() {
return toHints(BarcodeFormat.QR_CODE);
}
/**
* 转换为Zxing的二维码配置
*
* @param format 格式根据格式不同{@link #errorCorrection}的值类型有所不同
* @return 配置
*/
public HashMap<EncodeHintType, Object> toHints(final BarcodeFormat format) {
// 配置 // 配置
final HashMap<EncodeHintType, Object> hints = new HashMap<>(); final HashMap<EncodeHintType, Object> hints = new HashMap<>();
if (null != this.charset) { if (null != this.charset) {

View File

@ -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<Image, String> {
private final Map<DecodeHintType, Object> hints;
/**
* 创建二维码条形码等解码器用于将二维码条形码等解码为所代表的内容字符串
*
* @param isTryHarder 是否优化精度
* @param isPureBarcode 是否使用复杂模式扫描带logo的二维码设为true
* @return QrDecoder
*/
public static QrDecoder of(final boolean isTryHarder, final boolean isPureBarcode) {
return of(buildHints(isTryHarder, isPureBarcode));
}
/**
* 创建二维码条形码等解码器
*
* @param hints 自定义扫码配置包括算法编码复杂模式等
* @return QrDecoder
*/
public static QrDecoder of(final Map<DecodeHintType, Object> hints) {
return new QrDecoder(hints);
}
/**
* 构造
*
* @param hints 自定义扫码配置包括算法编码复杂模式等
*/
public QrDecoder(Map<DecodeHintType, Object> 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<DecodeHintType, Object> buildHints(final boolean isTryHarder, final boolean isPureBarcode) {
final HashMap<DecodeHintType, Object> hints = new HashMap<>(3, 1);
hints.put(DecodeHintType.CHARACTER_SET, CharsetUtil.NAME_UTF_8);
// 优化精度
if (isTryHarder) {
hints.put(DecodeHintType.TRY_HARDER, true);
}
// 复杂模式开启PURE_BARCODE模式
if (isPureBarcode) {
hints.put(DecodeHintType.PURE_BARCODE, true);
}
return hints;
}
}

View File

@ -0,0 +1,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<CharSequence, BitMatrix> {
/**
* 创建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;
}
}

View File

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

View File

@ -17,6 +17,11 @@ public class QrSVG {
private final BitMatrix matrix; private final BitMatrix matrix;
private final QrConfig qrConfig; private final QrConfig qrConfig;
/**
* 构造
* @param matrix {@link BitMatrix}
* @param qrConfig {@link QrConfig}
*/
public QrSVG(BitMatrix matrix, QrConfig qrConfig) { public QrSVG(BitMatrix matrix, QrConfig qrConfig) {
this.matrix = matrix; this.matrix = matrix;
this.qrConfig = qrConfig; this.qrConfig = qrConfig;

View File

@ -2,7 +2,9 @@ package cn.hutool.extra.qrcode;
import cn.hutool.core.codec.Base64; import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Console; import cn.hutool.core.lang.Console;
import cn.hutool.core.text.StrUtil;
import cn.hutool.swing.img.ImgUtil; import cn.hutool.swing.img.ImgUtil;
import com.google.zxing.BarcodeFormat; import com.google.zxing.BarcodeFormat;
import com.google.zxing.datamatrix.encoder.SymbolShapeHint; import com.google.zxing.datamatrix.encoder.SymbolShapeHint;
@ -14,6 +16,7 @@ import org.junit.Test;
import java.awt.Color; import java.awt.Color;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.nio.charset.StandardCharsets;
/** /**
* 二维码工具类单元测试 * 二维码工具类单元测试
@ -31,7 +34,7 @@ public class QrCodeUtilTest {
@Test @Test
@Ignore @Ignore
public void generateCustomTest() { public void generateCustomTest() {
final QrConfig config = new QrConfig(); final QrConfig config = QrConfig.of();
config.setMargin(0); config.setMargin(0);
config.setForeColor(Color.CYAN); config.setForeColor(Color.CYAN);
// 背景色透明 // 背景色透明
@ -44,7 +47,7 @@ public class QrCodeUtilTest {
@Test @Test
@Ignore @Ignore
public void generateWithLogoTest() { 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"; final String targetPath = FileUtil.isWindows() ? "d:/test/qrcodeWithLogo.jpg" : "~/Desktop/hutool/qrcodeWithLogo.jpg";
QrCodeUtil.generate(// QrCodeUtil.generate(//
"https://hutool.cn/", // "https://hutool.cn/", //
@ -68,17 +71,22 @@ public class QrCodeUtilTest {
} }
@Test @Test
public void generateAsBase64Test() { public void generateAsBase64AndDecodeTest() {
final String base64 = QrCodeUtil.generateAsBase64("https://hutool.cn/", new QrConfig(400, 400), "png"); final String url = "https://hutool.cn/";
String base64 = QrCodeUtil.generateAsBase64DataUri(url, new QrConfig(400, 400), "png");
Assert.assertNotNull(base64); 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 @Test
@Ignore @Ignore
public void generateAsBase64Test2() { public void generateAsBase64Test2() {
final byte[] bytes = FileUtil.readBytes(new File("d:/test/qr.png")); final byte[] bytes = FileUtil.readBytes(new File("d:/test/qr.png"));
final String encode = Base64.encode(bytes); final String base641 = QrCodeUtil.generateAsBase64DataUri("https://hutool.cn/",
final String base641 = QrCodeUtil.generateAsBase64("https://hutool.cn/", new QrConfig(400, 400), "png", encode); new QrConfig(400, 400), "png", bytes);
Assert.assertNotNull(base641); Assert.assertNotNull(base641);
} }
@ -91,7 +99,7 @@ public class QrCodeUtilTest {
@Test @Test
public void pdf417Test() { 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); Assert.assertNotNull(image);
} }
@ -99,11 +107,50 @@ public class QrCodeUtilTest {
public void generateDataMatrixTest() { public void generateDataMatrixTest() {
final QrConfig qrConfig = QrConfig.of(); final QrConfig qrConfig = QrConfig.of();
qrConfig.setShapeHint(SymbolShapeHint.FORCE_RECTANGLE); 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); Assert.assertNotNull(image);
final QrConfig config = QrConfig.of(); final QrConfig config = QrConfig.of();
config.setShapeHint(SymbolShapeHint.FORCE_SQUARE); 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); 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);
}
} }

View File

@ -10,8 +10,6 @@ import java.util.LinkedHashMap;
* @author Tom Xin * @author Tom Xin
*/ */
public class Ansi8bitMapping extends AnsiLabMapping { 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, 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, 0x000080, 0x800080, 0x008080, 0xc0c0c0, 0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff,
0x00ffff, 0xffffff, 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, 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, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada,
0xe4e4e4, 0xeeeeee }; 0xe4e4e4, 0xeeeeee };
public static final Ansi8bitMapping INSTANCE = new Ansi8bitMapping();
public Ansi8bitMapping() { public Ansi8bitMapping() {
ansiLabMap = new LinkedHashMap<>(256, 1); ansiLabMap = new LinkedHashMap<>(256, 1);
for (int i = 0; i < ANSI_8BIT_COLOR_CODE_LOOKUP.length; i++) { for (int i = 0; i < ANSI_8BIT_COLOR_CODE_LOOKUP.length; i++) {

View File

@ -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)); 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颜色由于取最接近颜色故可能有色差 * AWT的{@link Color}颜色转换为ANSI颜色由于取最接近颜色故可能有色差
* *