目录
- 一、序言
- 二、找资料
- 1、寻觅文档
- 2、寻觅代码
- 三、代码示例
- 1、简单的二维码
- 2、带颜色的二维码
- 3、带logo的二维码
- 四、工具类封装
一、序言
之前在做头马演讲俱乐部哼哈官可视化汇报报告时,为了方便大家移动端查看可视化报告,而不是通过点击链接这种生硬的方式,专门研究了下如何生成二维码。
在Github
上有个第三方库,来自google的 zxing,这个库能生成各种条形码,如:二维码、支付条形码等。下面是zxing
支持的一些格式:
今天我们重点研究一下关于通过zxing
生成二维码的一些参数,以及从哪里找到这些文档和介绍。
二、找资料
1、寻觅文档
有点小尴尬的是,zxing
生成二维码Java
相关的API并没有明确的相关解释以及使用示例,笔者也是在zxing
的github上发现了一个在线生成二维码的网站,在线二维码生成器。
备注:这个网站需要翻墙,不然访问不了。
然后在Github上找到了这个在线二维码生成器的相关参数描述,这里就勉为其难地翻译一波吧。
这里重点介绍一下图中error_correction_level
这个参数,二维码支持4种级别的错误校正,错误校正主要用来做缺失、误读或者遮盖数据修复。二维码越冗余也就意味着只能存储更少的数据,错误校正级别主要有如下4级:
- L - 默认级别,可以做到即使丢失
7%
的数据也能正常识别。 - M - 中级,可以做到即使丢失
15%
的数据也能正常识别。 - Q - 中上级,可以做到即使丢失
25%
的数据也能正常识别。 - H - 高级,可以做到即使丢失
30%
的数据也能正常识别。
备注:所谓错误校正,说句大白话就是如果二维码有缺失,或者部分二维码有遮挡,还能不能被正常识别。级别越高,容错率也就越高。
当我们用不同级别生成二维码时,二维码的密集度也会不一样,错误校正级别越高,生成的二维码越分散。
2、寻觅代码
前面那个需要翻墙的在线二维码生成器实际上就是一个古老的Servlet应用,在zxing
的源码里找到zxingorg
这个目录,这就是那个在线二维码生成器的源码了。生成二维码的核心逻辑在ChartServlet
这个类里,如下:
接下来我们直接在doEncode
方法里就能找到生成二维码的逻辑,还是非常简单的,代码如下:
private static void doEncode(HttpServletRequest request, HttpServletResponse response, boolean isPost)throws IOException {ChartServletRequestParameters parameters;try {parameters = doParseParameters(request, isPost);} catch (IllegalArgumentException | NullPointerException e) {response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.toString());return;}Map<EncodeHintType,Object> hints = new EnumMap<>(EncodeHintType.class);hints.put(EncodeHintType.MARGIN, parameters.getMargin());if (!StandardCharsets.ISO_8859_1.equals(parameters.getOutputEncoding())) {// Only set if not QR code defaulthints.put(EncodeHintType.CHARACTER_SET, parameters.getOutputEncoding().name());}hints.put(EncodeHintType.ERROR_CORRECTION, parameters.getEcLevel());BitMatrix matrix;try {matrix = new QRCodeWriter().encode(parameters.getText(),BarcodeFormat.QR_CODE,parameters.getWidth(),parameters.getHeight(),hints);} catch (WriterException we) {response.sendError(HttpServletResponse.SC_BAD_REQUEST, we.toString());return;}String requestURI = request.getRequestURI();if (requestURI == null) {response.sendError(HttpServletResponse.SC_BAD_REQUEST);return;}int lastDot = requestURI.lastIndexOf('.');String imageFormat;if (lastDot > 0) {imageFormat = requestURI.substring(lastDot + 1).toUpperCase(Locale.ROOT);// Special-case jpg -> JPEGif ("JPG".equals(imageFormat)) {imageFormat = "JPEG";}} else {imageFormat = "PNG";}String contentType;switch (imageFormat) {case "PNG":contentType = "image/png";break;case "JPEG":contentType = "image/jpeg";break;case "GIF":contentType = "image/gif";break;default:throw new IllegalArgumentException("Unknown format " + imageFormat);}ByteArrayOutputStream imageOut = new ByteArrayOutputStream(1024);MatrixToImageWriter.writeToStream(matrix, imageFormat, imageOut);byte[] imageData = imageOut.toByteArray();response.setContentType(contentType);response.setContentLength(imageData.length);response.setHeader("Cache-Control", "public");response.getOutputStream().write(imageData);}
三、代码示例
首先引入zxing
的依赖,如下:
<dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.5.0</version></dependency>
1、简单的二维码
由于平台上二维码图片会被当做违规,无法查看,因此这里就不贴二维码示例了。
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name());
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);String content = "https://www.baidu.com";
String qrCodePath = "C:\\Users\\mc\\Desktop\\qrcode.png";QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, 300, 300, hints);
MatrixToImageWriter.writeToPath(bitMatrix, "png", Paths.get(qrCodePath));
2、带颜色的二维码
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name());
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);String content = "https://www.baidu.com";
String qrCodePath = "C:\\Users\\mc\\Desktop\\qrcode.png";// 生成带颜色的二维码, 指定前景色和后景色即可
MatrixToImageConfig matrixToImageConfig = new MatrixToImageConfig(Color.GREEN.getRGB(), Color.WHITE.getRGB());QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, 300, 300, hints);
MatrixToImageWriter.writeToPath(bitMatrix, "png", Paths.get(qrCodePath), matrixToImageConfig);
备注:带颜色的二维码只需加上
MatrixToImageConfig
参数即可。
3、带logo的二维码
我们都知道,微信二维码中间是不是带了个微信的logo,我们同样能实现,这里更多的是awt
对图片的相关操作。
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name());
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);String content = "https://www.baidu.com";
String qrCodePath = "C:\\Users\\mc\\Desktop\\qrcode.png";
String logoPath = "C:\\Users\\mc\\Desktop\\logo_mini.png";QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, 300, 300, hints);// 写入输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix, DEFAULT_FORMAT, baos);ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
BufferedImage qrCodeImage = ImageIO.read(bais);
Graphics2D graphics2D = qrCodeImage.createGraphics();BufferedImage logoImage = ImageIO.read(Paths.get(logoPath).toFile());
// 这里将logo的位置居中
int logoX = (qrCodeImage.getWidth() - logoImage.getWidth()) / 2;
int logoY = (qrCodeImage.getHeight() - logoImage.getHeight()) / 2;
// 在二维码画布上绘图
graphics2D.drawImage(logoImage, null, logoX, logoY);
graphics2D.dispose();// 输出绘制后logo的二维码图片
ImageIO.write(qrCodeImage, "png", Paths.get(qrCodePath).toFile());
备注:这里需要注意一下logo的图片尺寸,占用太大会导致二维码无法识别,同时最好将
error_correction_level
设置为高级,增加容错率。
四、工具类封装
核心代码比较简单,随手简单封装了一下,zxing
还是很强大的,点个赞。
/*** @author 刘亚楼* @date 2022/9/22*/
public abstract class QrCodeUtils {/*** 默认生成的文件*/private static final String DEFAULT_FORMAT = "png";/*** 默认额外参数:可设置字符集、容错级别*/private static final Map<EncodeHintType, Object> DEFAULT_HINTS = new HashMap<>();static {DEFAULT_HINTS.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name());DEFAULT_HINTS.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);}/*** 生成绿码* @param content 二维码展示内容* @param width 二维码图片宽度* @param height 二维码图片高度* @return 图片二进制流* @throws Exception*/public static byte[] generateGreenQrCodeAsByteArray(String content, int width, int height) throws Exception {return generateQrCodeAsByteArray(content, width, height, Color.GREEN.getRGB(), Color.WHITE.getRGB());}/*** 生成二维码并转为二进制流* @param content 二维码展示内容* @param width 二维码图片宽度* @param height 二维码图片高度* @param onColorAsRGB 二维码数据图案颜色* @param offColorAsRGB 二维码背景色* @return 图片二进制流* @throws Exception*/public static byte[] generateQrCodeAsByteArray(String content, int width, int height, int onColorAsRGB, int offColorAsRGB) throws Exception {ByteArrayOutputStream baos = new ByteArrayOutputStream();generateQrCodeAsByteArray(baos, content, width, height, onColorAsRGB, offColorAsRGB);return baos.toByteArray();}/*** 生成二维码* @param os 输出流* @param content 二维码展示内容* @param width 二维码图片宽度* @param height 二维码图片高度* @param onColorAsRGB 二维码数据图案颜色* @param offColorAsRGB 二维码背景色* @throws Exception*/public static void generateQrCodeAsByteArray(OutputStream os, String content, int width, int height, int onColorAsRGB, int offColorAsRGB) throws Exception {QRCodeWriter qrCodeWriter = new QRCodeWriter();BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, width, height, DEFAULT_HINTS);MatrixToImageConfig matrixToImageConfig = new MatrixToImageConfig(onColorAsRGB, offColorAsRGB);MatrixToImageWriter.writeToStream(bitMatrix, DEFAULT_FORMAT, os, matrixToImageConfig);}/*** 生成二维码* @param path 文件路径* @param content 二维码展示内容* @param width 二维码图片宽度* @param height 二维码图片高度* @param onColorAsRGB 二维码数据图案颜色* @param offColorAsRGB 二维码背景色* @throws Exception*/public static void generateQrCodeAsByteArray(String path, String content, int width, int height, int onColorAsRGB, int offColorAsRGB) throws Exception {QRCodeWriter qrCodeWriter = new QRCodeWriter();BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, width, height, DEFAULT_HINTS);MatrixToImageConfig matrixToImageConfig = new MatrixToImageConfig(onColorAsRGB, offColorAsRGB);MatrixToImageWriter.writeToPath(bitMatrix, DEFAULT_FORMAT, Paths.get(path), matrixToImageConfig);}public static byte[] attachLogoInTheMiddle(InputStream qrCodeIs, InputStream logoIs) throws Exception {ByteArrayOutputStream baos = new ByteArrayOutputStream();attachLogoInTheMiddle(qrCodeIs, logoIs, baos);return baos.toByteArray();}/*** 二维码中间插入logo* @param qrCodeIs 二维码输入流* @param logoIs logo输入流* @param dest 输出路径* @throws Exception*/public static void attachLogoInTheMiddle(InputStream qrCodeIs, InputStream logoIs, OutputStream dest) throws Exception {BufferedImage qrCodeImage = ImageIO.read(qrCodeIs);BufferedImage logoImage = ImageIO.read(logoIs);Graphics2D graphics2D = qrCodeImage.createGraphics();// 这里将logo的位置居中int logoX = (qrCodeImage.getWidth() - logoImage.getWidth()) / 2;int logoY = (qrCodeImage.getHeight() - logoImage.getHeight()) / 2;graphics2D.drawImage(logoImage, null, logoX, logoY);graphics2D.dispose();ImageIO.write(qrCodeImage, DEFAULT_FORMAT, dest);}/*** 重置图片大小* @param rawImage 原图片* @param targetWidth 目标图片宽度* @param targetHeight 目标图片高度* @return*/public static BufferedImage resizeImage(BufferedImage rawImage, int targetWidth, int targetHeight) {Image scaledImage = rawImage.getScaledInstance(targetWidth, targetHeight, Image.SCALE_AREA_AVERAGING);BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);resizedImage.getGraphics().drawImage(scaledImage, 0, 0, null);return resizedImage;}}