All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.polaris.image.qrcode.QrCodes Maven / Gradle / Ivy

package io.polaris.image.qrcode;

import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.common.HybridBinarizer;
import io.polaris.core.codec.Base64;
import io.polaris.core.io.Filenames;
import io.polaris.core.io.IO;
import io.polaris.core.io.ansi.AnsiColors;
import io.polaris.core.io.ansi.AnsiElement;
import io.polaris.core.io.ansi.AnsiEncoder;
import io.polaris.core.io.ansi.ForeOrBack;
import io.polaris.core.io.image.ImageEditor;
import io.polaris.core.io.image.Images;
import io.polaris.core.string.Strings;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * 基于Zxing的二维码工具类,支持:
 * 
    *
  • 二维码生成和识别,见{@link BarcodeFormat#QR_CODE}
  • *
  • 条形码生成和识别,见{@link BarcodeFormat#CODE_39}等很多标准格式
  • *
* * @author Qt * @since 1.8 */ public class QrCodes { public static final String QR_TYPE_SVG = "svg";// SVG矢量图格式 public static final String QR_TYPE_TXT = "txt";// Ascii Art字符画文本 private static final AnsiColors ansiColors = new AnsiColors(AnsiColors.BitDepth.EIGHT); /** * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 * * @param content 内容 * @param qrConfig 二维码配置,包括宽度、高度、边距、颜色等 * @param targetType 类型(图片扩展名),见{@link #QR_TYPE_SVG}、 {@link #QR_TYPE_TXT}、{@link Images} * @param logoBase64 logo 图片的 base64 编码 * @return 图片 Base64 编码字符串 */ public static String generateAsBase64(String content, QrConfig qrConfig, String targetType, String logoBase64) throws IOException { return generateAsBase64(content, qrConfig, targetType, Base64.decode(logoBase64)); } /** * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 * * @param content 内容 * @param qrConfig 二维码配置,包括宽度、高度、边距、颜色等 * @param targetType 类型(图片扩展名),见{@link #QR_TYPE_SVG}、 {@link #QR_TYPE_TXT}、{@link Images} * @param logo logo 图片的byte[] * @return 图片 Base64 编码字符串 */ public static String generateAsBase64(String content, QrConfig qrConfig, String targetType, byte[] logo) throws IOException { return generateAsBase64(content, qrConfig, targetType, Images.toImage(logo)); } /** * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 * * @param content 内容 * @param qrConfig 二维码配置,包括宽度、高度、边距、颜色等 * @param targetType 类型(图片扩展名),见{@link #QR_TYPE_SVG}、 {@link #QR_TYPE_TXT}、{@link Images} * @param logo logo 图片的byte[] * @return 图片 Base64 编码字符串 */ public static String generateAsBase64(String content, QrConfig qrConfig, String targetType, Image logo) throws IOException { qrConfig.setImg(logo); return generateAsBase64(content, qrConfig, targetType); } /** * 生成 Base64 编码格式的二维码,以 String 形式表示 * *

* 输出格式为: data:image/[type];base64,[data] *

* * @param content 内容 * @param qrConfig 二维码配置,包括宽度、高度、边距、颜色等 * @param targetType 类型(图片扩展名),见{@link #QR_TYPE_SVG}、 {@link #QR_TYPE_TXT}、{@link Images} * @return 图片 Base64 编码字符串 */ public static String generateAsBase64(String content, QrConfig qrConfig, String targetType) throws IOException { String result; switch (targetType) { case QR_TYPE_SVG: String svg = generateAsSvg(content, qrConfig); result = svgToBase64(svg); break; case QR_TYPE_TXT: String txt = generateAsAsciiArt(content, qrConfig); result = txtToBase64(txt); break; default: final BufferedImage img = generate(content, qrConfig); result = Images.toBase64DataUri(img, targetType); break; } return result; } private static String txtToBase64(String txt) { return Images.toBase64DataUri("text/plain", Base64.encodeToString(txt.getBytes(StandardCharsets.UTF_8))); } private static String svgToBase64(String svg) { return Images.toBase64DataUri("image/svg+xml", Base64.encodeToString(svg.getBytes(StandardCharsets.UTF_8))); } /** * @param content 内容 * @param qrConfig 二维码配置,包括宽度、高度、边距、颜色等 * @return SVG矢量图(字符串) * @since 5.8.6 */ public static String generateAsSvg(String content, QrConfig qrConfig) throws IOException { final BitMatrix bitMatrix = encode(content, qrConfig); return toSVG(bitMatrix, qrConfig); } /** * 生成ASCII Art字符画形式的二维码 * * @param content 内容 * @return ASCII Art字符画形式的二维码字符串 * @since 5.8.6 */ public static String generateAsAsciiArt(String content) throws IOException { return generateAsAsciiArt(content, 0, 0, 1); } /** * 生成ASCII Art字符画形式的二维码 * * @param content 内容 * @param qrConfig 二维码配置,仅宽度、高度、边距配置有效 * @return ASCII Art字符画形式的二维码 * @since 5.8.6 */ public static String generateAsAsciiArt(String content, QrConfig qrConfig) throws IOException { final BitMatrix bitMatrix = encode(content, qrConfig); return toAsciiArt(bitMatrix, qrConfig); } /** * @param content 内容 * @param width 宽度(单位:字符▄的大小) * @param height 高度(单位:字符▄的大小) * @param margin 边距大小(1~4) * @return ASCII Art字符画形式的二维码 * @since 5.8.6 */ public static String generateAsAsciiArt(String content, int width, int height, int margin) throws IOException { QrConfig qrConfig = new QrConfig(width, height).setMargin(margin); return generateAsAsciiArt(content, qrConfig); } /** * 生成PNG格式的二维码图片,以byte[]形式表示 * * @param content 内容 * @param width 宽度(单位:像素) * @param height 高度(单位:像素) * @return 图片的byte[] * @since 4.0.10 */ public static byte[] generatePng(String content, int width, int height) throws IOException { final ByteArrayOutputStream out = new ByteArrayOutputStream(); generate(content, width, height, Images.IMAGE_TYPE_PNG, out); return out.toByteArray(); } /** * 生成PNG格式的二维码图片,以byte[]形式表示 * * @param content 内容 * @param config 二维码配置,包括宽度、高度、边距、颜色等 * @return 图片的byte[] * @since 4.1.2 */ public static byte[] generatePng(String content, QrConfig config) throws IOException { final ByteArrayOutputStream out = new ByteArrayOutputStream(); generate(content, config, Images.IMAGE_TYPE_PNG, out); return out.toByteArray(); } /** * 生成二维码到文件,二维码图片格式取决于文件的扩展名 * * @param content 文本内容 * @param width 宽度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小) * @param height 高度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小) * @param targetFile 目标文件,扩展名决定输出格式 * @return 目标文件 */ public static File generate(String content, int width, int height, File targetFile) throws IOException { String extName = Filenames.extName(targetFile); switch (extName) { case QR_TYPE_SVG: String svg = generateAsSvg(content, new QrConfig(width, height)); IO.writeString(targetFile, StandardCharsets.UTF_8, svg); break; case QR_TYPE_TXT: String txt = generateAsAsciiArt(content, new QrConfig(width, height)); IO.writeString(targetFile, StandardCharsets.UTF_8, txt); break; default: final BufferedImage image = generate(content, width, height); Images.write(image, targetFile); break; } return targetFile; } /** * 生成二维码到文件,二维码图片格式取决于文件的扩展名 * * @param content 文本内容 * @param config 二维码配置,包括宽度、高度、边距、颜色等 * @param targetFile 目标文件,扩展名决定输出格式 * @return 目标文件 * @since 4.1.2 */ public static File generate(String content, QrConfig config, File targetFile) throws IOException { String extName = Filenames.extName(targetFile); switch (extName) { case QR_TYPE_SVG: final String svg = generateAsSvg(content, config); IO.writeString(targetFile, StandardCharsets.UTF_8, svg); break; case QR_TYPE_TXT: final String txt = generateAsAsciiArt(content, config); IO.writeString(targetFile, StandardCharsets.UTF_8, txt); break; default: final BufferedImage image = generate(content, config); Images.write(image, targetFile); break; } return targetFile; } /** * 生成二维码到输出流 * * @param content 文本内容 * @param width 宽度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小) * @param height 高度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小) * @param targetType 类型(图片扩展名),见{@link #QR_TYPE_SVG}、 {@link #QR_TYPE_TXT}、{@link Images} * @param out 目标流 */ public static void generate(String content, int width, int height, String targetType, OutputStream out) throws IOException { switch (targetType) { case QR_TYPE_SVG: final String svg = generateAsSvg(content, new QrConfig(width, height)); IO.writeString(out, StandardCharsets.UTF_8, svg); break; case QR_TYPE_TXT: final String txt = generateAsAsciiArt(content, new QrConfig(width, height)); IO.writeString(out, StandardCharsets.UTF_8, txt); break; default: final BufferedImage image = generate(content, width, height); Images.write(image, targetType, out); break; } } /** * 生成二维码到输出流 * * @param content 文本内容 * @param config 二维码配置,包括宽度、高度、边距、颜色等 * @param targetType 类型(图片扩展名),见{@link #QR_TYPE_SVG}、 {@link #QR_TYPE_TXT}、{@link Images} * @param out 目标流 * @since 4.1.2 */ public static void generate(String content, QrConfig config, String targetType, OutputStream out) throws IOException { switch (targetType) { case QR_TYPE_SVG: final String svg = generateAsSvg(content, config); IO.writeString(out, StandardCharsets.UTF_8, svg); break; case QR_TYPE_TXT: final String txt = generateAsAsciiArt(content, config); IO.writeString(out, StandardCharsets.UTF_8, txt); break; default: final BufferedImage image = generate(content, config); Images.write(image, targetType, out); break; } } /** * 生成二维码图片 * * @param content 文本内容 * @param width 宽度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小) * @param height 高度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小) * @return 二维码图片(黑白) */ public static BufferedImage generate(String content, int width, int height) throws IOException { return generate(content, new QrConfig(width, height)); } /** * 生成二维码或条形码图片 * * @param content 文本内容 * @param format 格式,可选二维码或者条形码 * @param width 宽度(单位:像素) * @param height 高度(单位:像素) * @return 二维码图片(黑白) */ public static BufferedImage generate(String content, BarcodeFormat format, int width, int height) throws IOException { return generate(content, format, new QrConfig(width, height)); } /** * 生成二维码图片 * * @param content 文本内容 * @param config 二维码配置,包括宽度、高度、边距、颜色等 * @return 二维码图片(黑白) * @since 4.1.2 */ public static BufferedImage generate(String content, QrConfig config) throws IOException { return generate(content, BarcodeFormat.QR_CODE, config); } /** * 生成二维码或条形码图片
* 只有二维码时QrConfig中的图片才有效 * * @param content 文本内容 * @param format 格式,可选二维码、条形码等 * @param config 二维码配置,包括宽度、高度、边距、颜色等 * @return 二维码图片(黑白) * @since 4.1.14 */ public static BufferedImage generate(String content, BarcodeFormat format, QrConfig config) throws IOException { final BitMatrix bitMatrix = encode(content, format, config); final BufferedImage image = toImage(bitMatrix, config.foreColor != null ? config.foreColor : Color.BLACK.getRGB(), config.backColor); final Image logoImg = config.img; if (null != logoImg && BarcodeFormat.QR_CODE == format) { // 只有二维码可以贴图 final int qrWidth = image.getWidth(); final int qrHeight = image.getHeight(); int width; 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); } ImageEditor.from(image).pressImage(// ImageEditor.from(logoImg).round(0.3).getImg(), // 圆角 new Rectangle(width, height), // 1// ); } return image; } // ------------------------------------------------------------------------------------------------------------------- encode /** * 将文本内容编码为二维码 * * @param content 文本内容 * @param width 宽度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小) * @param height 高度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小) * @return {@link BitMatrix} */ public static BitMatrix encode(String content, int width, int height) throws IOException { return encode(content, BarcodeFormat.QR_CODE, width, height); } /** * 将文本内容编码为二维码 * * @param content 文本内容 * @param config 二维码配置,包括宽度、高度、边距、颜色等 * @return {@link BitMatrix} * @since 4.1.2 */ public static BitMatrix encode(String content, QrConfig config) throws IOException { return encode(content, BarcodeFormat.QR_CODE, config); } /** * 将文本内容编码为条形码或二维码 * * @param content 文本内容 * @param format 格式枚举 * @param width 宽度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小) * @param height 高度(单位:类型为一般图片或SVG时,单位是像素,类型为 Ascii Art 字符画时,单位是字符▄或▀的大小) * @return {@link BitMatrix} */ public static BitMatrix encode(String content, BarcodeFormat format, int width, int height) throws IOException { return encode(content, format, new QrConfig(width, height)); } /** * 将文本内容编码为条形码或二维码 * * @param content 文本内容 * @param format 格式枚举 * @param config 二维码配置,包括宽度、高度、边距、颜色等 * @return {@link BitMatrix} * @since 4.1.2 */ public static BitMatrix encode(String content, BarcodeFormat format, QrConfig config) throws IOException { final MultiFormatWriter multiFormatWriter = new MultiFormatWriter(); if (null == config) { // 默认配置 config = new QrConfig(); } BitMatrix bitMatrix; try { bitMatrix = multiFormatWriter.encode(content, format, config.width, config.height, config.toHints(format)); } catch (WriterException e) { throw new IOException(e); } return bitMatrix; } // ------------------------------------------------------------------------------------------------------------------- decode /** * 解码二维码或条形码图片为文本 * * @param qrCodeInputStream 二维码输入流 * @return 解码文本 */ public static String decode(InputStream qrCodeInputStream) throws IOException { return decode(Images.read(qrCodeInputStream)); } /** * 解码二维码或条形码图片为文本 * * @param qrCodeFile 二维码文件 * @return 解码文本 */ public static String decode(File qrCodeFile) throws IOException { return decode(Images.read(qrCodeFile)); } /** * 将二维码或条形码图片解码为文本 * * @param image {@link Image} 二维码图片 * @return 解码后的文本 */ public static String decode(Image image) { return decode(image, true, false); } /** * 将二维码或条形码图片解码为文本
* 此方法会尝试使用{@link HybridBinarizer}和{@link GlobalHistogramBinarizer}两种模式解析
* 需要注意部分二维码如果不带logo,使用PureBarcode模式会解析失败,此时须设置此选项为false。 * * @param image {@link Image} 二维码图片 * @param isTryHarder 是否优化精度 * @param isPureBarcode 是否使用复杂模式,扫描带logo的二维码设为true * @return 解码后的文本 * @since 4.3.1 */ public static String decode(Image image, boolean isTryHarder, boolean isPureBarcode) { return decode(image, buildHints(isTryHarder, isPureBarcode)); } /** * 将二维码或条形码图片解码为文本
* 此方法会尝试使用{@link HybridBinarizer}和{@link GlobalHistogramBinarizer}两种模式解析
* 需要注意部分二维码如果不带logo,使用PureBarcode模式会解析失败,此时须设置此选项为false。 * * @param image {@link Image} 二维码图片 * @param hints 自定义扫码配置,包括算法、编码、复杂模式等 * @return 解码后的文本 * @since 5.7.12 */ public static String decode(Image image, Map hints) { final MultiFormatReader formatReader = new MultiFormatReader(); formatReader.setHints(hints); final LuminanceSource source = new BufferedImageLuminanceSource(Images.toBufferedImage(image)); Result result = _decode(formatReader, new HybridBinarizer(source)); if (null == result) { result = _decode(formatReader, new GlobalHistogramBinarizer(source)); } return null != result ? result.getText() : null; } /** * BitMatrix转BufferedImage * * @param matrix BitMatrix * @param foreColor 前景色 * @param backColor 背景色(null表示透明背景) * @return BufferedImage * @since 4.1.2 */ public static BufferedImage toImage(BitMatrix matrix, int foreColor, Integer backColor) { final int width = matrix.getWidth(); final int height = matrix.getHeight(); BufferedImage image = new BufferedImage(width, height, null == backColor ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); 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; } /** * BitMatrix转SVG(字符串) * * @param matrix BitMatrix * @param qrConfig 二维码配置,包括宽度、高度、边距、颜色等 * @return SVG矢量图(字符串) * @since 5.8.6 */ public static String toSVG(BitMatrix matrix, QrConfig qrConfig) throws IOException { return toSVG(matrix, qrConfig.foreColor, qrConfig.backColor, qrConfig.img, qrConfig.getRatio()); } /** * BitMatrix转SVG(字符串) * * @param matrix BitMatrix * @param foreColor 前景色 * @param backColor 背景色(null表示透明背景) * @param logoImg LOGO图片 * @param ratio 二维码中的Logo缩放的比例系数,如5表示长宽最小值的1/5 * @return SVG矢量图(字符串) * @since 5.8.6 */ public static String toSVG(BitMatrix matrix, Integer foreColor, Integer backColor, Image logoImg, int ratio) throws IOException { StringBuilder sb = new StringBuilder(); int qrWidth = matrix.getWidth(); int qrHeight = matrix.getHeight(); 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 = Images.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; } StringBuilder result = new StringBuilder(); result.append("\n"); result.append(" \n"); if (Strings.isNotBlank(logoBase64)) { result.append("\n"); } result.append(""); return result.toString(); } /** * BitMatrix转ASCII Art字符画形式的二维码 * * @param bitMatrix BitMatrix * @param qrConfig QR设置 * @return ASCII Art字符画形式的二维码 * @since 5.8.6 */ public static String toAsciiArt(BitMatrix bitMatrix, QrConfig qrConfig) { final int width = bitMatrix.getWidth(); final int height = bitMatrix.getHeight(); final AnsiElement foreground = qrConfig.foreColor == null ? null : rgbToAnsi8BitElement(qrConfig.foreColor, ForeOrBack.FORE); final AnsiElement background = qrConfig.backColor == null ? null : rgbToAnsi8BitElement(qrConfig.backColor, ForeOrBack.BACK); 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 = bitMatrix.get(i, j); boolean bt = i + 1 >= height || bitMatrix.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(); } /* */ /** * rgb转AnsiElement * * @param rgb rgb颜色值 * @param foreOrBack 前景or背景 * @return AnsiElement * @since 5.8.6 */ private static AnsiElement rgbToAnsi8BitElement(int rgb, ForeOrBack foreOrBack) { return ansiColors.findClosest(new Color(rgb)).toAnsiElement(foreOrBack); } /** * 创建解码选项 * * @param isTryHarder 是否优化精度 * @param isPureBarcode 是否使用复杂模式,扫描带logo的二维码设为true * @return 选项Map */ private static Map buildHints(boolean isTryHarder, boolean isPureBarcode) { final HashMap hints = new HashMap<>(); hints.put(DecodeHintType.CHARACTER_SET, StandardCharsets.UTF_8); // 优化精度 if (isTryHarder) { hints.put(DecodeHintType.TRY_HARDER, true); } // 复杂模式,开启PURE_BARCODE模式 if (isPureBarcode) { hints.put(DecodeHintType.PURE_BARCODE, true); } return hints; } /** * 解码多种类型的码,包括二维码和条形码 * * @param formatReader {@link MultiFormatReader} * @param binarizer {@link Binarizer} * @return {@link Result} */ private static Result _decode(MultiFormatReader formatReader, Binarizer binarizer) { try { return formatReader.decodeWithState(new BinaryBitmap(binarizer)); } catch (NotFoundException e) { return null; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy