写这个篇文章是为了记录一下使用Java操作二维码的一些套路。因为在做这件事的时候,是遇到了一些问题的,这里记录一下,以备不时之需。

需求

根据文字内容生成二维码,在二维码中间加入logo图片,最后将二维码嵌入外部背景图中,写入到指定路径

效果

测试代码:

String content = "这是二维码内容";
String logoPath = "F:/test/qrcode/logo.png";
String backImagePath = "F:/test/qrcode/backImage.jpg";
String outputPath = "F:/test/qrcode/result.jpg";
boolean result = QrCodeUtil.createAndSaveQrCodeImg(content, logoPath, backImagePath, outputPath);

效果:

maven依赖

<dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.3.0</version>
</dependency>

源码

  1. 文字生成二维码

此处生成二维码图片,使用了自定义的encode方法和resizeAndCreateBufferedImage方法,相关源码和原因见下方的问题1

 /*** 生成二维码image** @param content 二维码内容* @return BufferedImage*/private static BufferedImage createQrCodeImage(String content) {return createQrCodeImage(content, 200, 200);}/*** 生成二维码image** @param content 二维码内容* @param width   宽度* @param height  长度* @return bufferImage*/private static BufferedImage createQrCodeImage(String content, int width, int height) {long start = System.currentTimeMillis();BufferedImage image = null;try {Hashtable<EncodeHintType, Object> hints = new Hashtable<>();hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);hints.put(EncodeHintType.CHARACTER_SET, "utf-8");hints.put(EncodeHintType.MARGIN, 1);// 使用自定义的方法,解决白边问题BitMatrix bitMatrix = encode(content, BarcodeFormat.QR_CODE, width, height, hints);// 重新调整大小,满足输入宽高image = resizeAndCreateBufferedImage(bitMatrix, width, height);} catch (WriterException e) {logger.error("QRCodeUtil-createQrCodeImage 生成二维码异常:", e);}logger.info("QRCodeUtil-createQrCodeImage end. cost:{} ", (System.currentTimeMillis() - start));return image;}
  1. 读取图片信息
 /*** 从图片路径读取生成image** @param imagePath 图片文件地址* @return bufferedImage*/private static BufferedImage createBufferedImage(String imagePath) {long start = System.currentTimeMillis();BufferedImage bi = null;try {BufferedImage tmpImage = ImageIO.read(new File(imagePath));// 防止写入jpg时出现失真异常,这里new一个新的image包一下bi = new BufferedImage(tmpImage.getWidth(), tmpImage.getHeight(), BufferedImage.TYPE_INT_RGB);bi.getGraphics().drawImage(tmpImage, 0, 0, null);} catch (IOException e) {logger.error("读取文件{} 生成bufferedImage失败:", imagePath, e);}logger.info("QRCodeUtil-createBufferedImage end. cost:{}", System.currentTimeMillis() - start);return bi;}
  1. 二维码中间填充logo图片
 /*** 二维码中间插入logo** @param codeImage 二维码image* @param logoImage logo image* @return 插入结果*/private static boolean combineCodeAndInnerLogo(BufferedImage codeImage, BufferedImage logoImage) {return combineCodeAndInnerLogo(codeImage, logoImage, true);}/*** 二维码中间插入logo** @param codeImage    二维码image* @param logoImage    logo image* @param needCompress 是否需要压缩* @return 插入结果*/private static boolean combineCodeAndInnerLogo(BufferedImage codeImage, Image logoImage, boolean needCompress) {boolean result;try {int logoWidth = logoImage.getWidth(null);int logoHeight = logoImage.getHeight(null);// 如果设置了需要压缩,则进行压缩if (needCompress) {logoWidth = logoWidth > LOGO_MAX_HEIGHT ? LOGO_MAX_WIDTH : logoWidth;logoHeight = logoHeight > LOGO_MAX_HEIGHT ? LOGO_MAX_HEIGHT : logoHeight;Image image = logoImage.getScaledInstance(logoWidth, logoHeight, Image.SCALE_SMOOTH);BufferedImage tag = new BufferedImage(logoWidth, logoHeight, BufferedImage.TYPE_INT_RGB);Graphics gMaker = tag.getGraphics();// 绘制缩小后的图gMaker.drawImage(image, 0, 0, null);gMaker.dispose();logoImage = image;}// 在中心位置插入logoGraphics2D codeImageGraphics = codeImage.createGraphics();int codeWidth = codeImage.getWidth();int codeHeight = codeImage.getHeight();int x = (codeWidth - logoWidth) / 2;int y = (codeHeight - logoHeight) / 2;codeImageGraphics.drawImage(logoImage, x, y, logoWidth, logoHeight, null);Shape shape = new RoundRectangle2D.Float(x, y, logoWidth, logoHeight, 6, 6);codeImageGraphics.setStroke(new BasicStroke(3f));codeImageGraphics.draw(shape);codeImageGraphics.dispose();result = true;} catch (Exception e) {logger.error("QRCodeUtil-combineCodeAndInnerLogo 二维码中间插入logo失败:", e);result = false;}return result;}
  1. 将背景图填充上生成的二维码
 /*** 合成二维码image和背景图image** @param codeImage 二维码image* @param backImage 背景图image*/private static BufferedImage combineCodeAndBackImage(BufferedImage codeImage, BufferedImage backImage) {return combineCodeAndBackImage(codeImage, backImage, -1, 100);}/*** 合成二维码image和背景图image,指定二维码底部距离背景图底部的距离** @param codeImage    二维码image* @param backImage    背景图image* @param marginLeft   二维码距离背景图左边距离,如果为-1,则左右居中* @param marginBottom 二维码距离背景图底部距离* @return bufferedImage*/private static BufferedImage combineCodeAndBackImage(BufferedImage codeImage, BufferedImage backImage, int marginLeft, int marginBottom) {long start = System.currentTimeMillis();Graphics2D backImageGraphics = backImage.createGraphics();// 确定二维码在背景图的左上角坐标int x = marginLeft;if (marginLeft == -1) {x = (backImage.getWidth() - codeImage.getWidth()) / 2;}int y = backImage.getHeight() - codeImage.getHeight() - marginBottom;// 组合绘图backImageGraphics.drawImage(codeImage, x, y, codeImage.getWidth(), codeImage.getHeight(), null);backImageGraphics.dispose();logger.info("QRCodeUtil-combineCodeAndBackImage end. cost:{}", System.currentTimeMillis() - start);return backImage;}
  1. 保存图片文件到指定路径
 /*** 保存图片文件到指定路径** @param image      图片image* @param outputPath 指定路径* @return 操作结果*/private static boolean imageSaveToFile(BufferedImage image, String outputPath) {boolean result;try {//  为了保证大图背景不变色,formatName必须为"png"ImageIO.write(image, "png", new File(outputPath));result = true;} catch (IOException e) {logger.error("QRCodeUtil-imageSaveToFile 保存图片到{} 失败:,", outputPath, e);result = false;}return result;}

问题

下面列举一下当时遇到的一些问题

  1. 生成的二维码白边很大
    默认使用zxing生成的二维码可以指定二维码长宽,但是整个图片规格是固定的,只能是固定的几个规格。这就导致如果我们需要指定生成二维码长宽的话,外边框会有留白。具体原因可以网上搜索,这里不赘述。
    解决方法是重写zxing相应的方法(com.google.zxing.qrcode.QRCodeWriter#encode(java.lang.String,com.google.zxing.BarcodeFormat, int, int, java.util.Map)),重新缩放调整二维码大小
 /*** 修改encode生成逻辑,删除白边* 源码见com.google.zxing.qrcode.QRCodeWriter#encode(java.lang.String,* com.google.zxing.BarcodeFormat, int, int, java.util.Map)** @param contents 二维码内容* @param format   格式* @param width    宽度* @param height   长度* @param hints    hints* @return BitMatrix* @throws WriterException exception*/private static BitMatrix encode(String contents, BarcodeFormat format, int width, int height,Hashtable<EncodeHintType, ?> hints) throws WriterException {if (contents.isEmpty()) {throw new IllegalArgumentException("Found empty contents");}if (format != BarcodeFormat.QR_CODE) {throw new IllegalArgumentException("Can only encode QR_CODE, but got " + format);}if (width < 0 || height < 0) {throw new IllegalArgumentException("Requested dimensions are too small: " + width + 'x' +height);}ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;int quietZone = QUIET_ZONE_SIZE;if (hints != null) {if (hints.containsKey(EncodeHintType.ERROR_CORRECTION)) {errorCorrectionLevel = ErrorCorrectionLevel.valueOf(hints.get(EncodeHintType.ERROR_CORRECTION).toString());}if (hints.containsKey(EncodeHintType.MARGIN)) {quietZone = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString());}}QRCode code = Encoder.encode(contents, errorCorrectionLevel, hints);return renderResult(code, width, height, quietZone);}/*** 对 zxing 的 QRCodeWriter 进行扩展, 解决白边过多的问题。去除白边的主要逻辑** @param code      qrcode* @param width     期望宽度* @param height    期望高度* @param quietZone quietZone* @return BitMatrix*/private static BitMatrix renderResult(QRCode code, int width, int height, int quietZone) {ByteMatrix input = code.getMatrix();if (input == null) {throw new IllegalStateException();}// xxx 二维码宽高相等, 即 qrWidth == qrHeightint inputWidth = input.getWidth();int inputHeight = input.getHeight();int qrWidth = inputWidth + (quietZone * 2);int qrHeight = inputHeight + (quietZone * 2);// 白边过多时, 缩放int minSize = Math.min(width, height);int scale = calculateScale(qrWidth, minSize);if (scale > 0) {int padding, tmpValue;// 计算边框留白padding = (minSize - qrWidth * scale) / QUIET_ZONE_SIZE * quietZone;tmpValue = qrWidth * scale + padding;if (width == height) {width = tmpValue;height = tmpValue;} else if (width > height) {width = width * tmpValue / height;height = tmpValue;} else {height = height * tmpValue / width;width = tmpValue;}}int outputWidth = Math.max(width, qrWidth);int outputHeight = Math.max(height, qrHeight);int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;int topPadding = (outputHeight - (inputHeight * multiple)) / 2;BitMatrix output = new BitMatrix(outputWidth, outputHeight);for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {// Write the contents of this row of the barcodefor (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {if (input.get(inputX, inputY) == 1) {output.setRegion(outputX, outputY, multiple, multiple);}}}return output;}/*** 如果留白超过15% , 则需要缩放* (15% 可以根据实际需要进行修改)** @param qrCodeSize 二维码大小* @param expectSize 期望输出大小* @return 返回缩放比例, <= 0 则表示不缩放, 否则指定缩放参数*/private static int calculateScale(int qrCodeSize, int expectSize) {if (qrCodeSize >= expectSize) {return 0;}int scale = expectSize / qrCodeSize;int abs = expectSize - scale * qrCodeSize;if (abs < expectSize * 0.15) {return 0;}return scale;}/*** 缩放调整二维码大小,使之符合期望大小** @param matrix matrix* @param width  期望宽度* @param height 期望高度* @return bufferedImage*/private static BufferedImage resizeAndCreateBufferedImage(BitMatrix matrix, int width, int height) {int qrCodeWidth = matrix.getWidth();int qrCodeHeight = matrix.getHeight();BufferedImage qrCode = new BufferedImage(qrCodeWidth, qrCodeHeight, BufferedImage.TYPE_INT_RGB);for (int x = 0; x < qrCodeWidth; x++) {for (int y = 0; y < qrCodeHeight; y++) {qrCode.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);}}// 若二维码的实际宽高和预期的宽高不一致, 则缩放if (qrCodeWidth != width || qrCodeHeight != height) {BufferedImage tmp = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);tmp.getGraphics().drawImage(qrCode.getScaledInstance(width, height,java.awt.Image.SCALE_SMOOTH), 0, 0, null);qrCode = tmp;}return qrCode;}

然后在调用生成二维码时,使用自定义的方法

  1. 生成的图片有颜色失真现象
    我在使用的时候,如果生成的图片存储为jpg格式时,可能会出现图片颜色异常的情况。这是因为jpg格式采用了有损压缩,会导致图片失真。

解决方法是在保存到本地时(imageSaveToFile),存储为png格式

//  为了保证大图背景不变色,formatName必须为"png"
ImageIO.write(image, "png", new File(outputPath));

这种方法有一个不足,就是如果原图是png格式的话,图片一般都比较大,比较占用本地内存,网络传输时,也会比较慢,影响体验。

如果我们将原图转为jpg格式,可以有效减少图片的大小,生成的图片大小也会相应的减少。但是测试发现也可能出现图片失真的情况。后来发现,在读取图片文件时(createBufferedImage),转存一下就可以解决这个问题。(具体为啥会这样,如果有人知道,欢迎指教)

// 防止写入jpg时出现失真异常,这里new一个新的image包一下
bi = new BufferedImage(tmpImage.getWidth(), tmpImage.getHeight(), BufferedImage.TYPE_INT_RGB);
bi.getGraphics().drawImage(tmpImage, 0, 0, null);

源码地址

https://download.csdn.net/download/somehow1002/12262631

Java二维码图片处理相关推荐

  1. Atitit java 二维码识别 图片识别

    Atitit java 二维码识别 图片识别 1.1. 解码1 1.2. 首先,我们先说一下二维码一共有40个尺寸.官方叫版本Version.1 1.3. 二维码的样例:2 1.4. 定位图案2 1. ...

  2. 使用Java生成二维码图片(亲测)

    下面我来分享两种生成二维码图片的方法. 第一种,填入你扫描二维码要跳转的网址直接生成二维码 第一步:导入相关的包 1 <dependency> 2 <groupId>com.g ...

  3. JAVA使用barcode4j生成条形码和二维码图片以及带logo的二维码,验证码图片

    二维码 1.Maven引入barcode4j依赖 <!-- 条形码生成 --><dependency><groupId>net.sf.barcode4j</g ...

  4. java生成二维码图片(有logo),并在图片下方附文字

    logo配置类 /*** Created by Amber Wang on 2017/11/27 17:25.*/import java.awt.*;public class LogoConfig { ...

  5. java实现后台直接生成二维码图片并直接上传到七牛云上面

    java实现后台直接生成二维码图片并直接上传到七牛云上面 需求:后台是直接根据唯一核销码生成图片,然后的话直接上传到七牛云,数据库只是保存地址,一开始也想错了,想成了创建临时文件存储生成的图片再上传到 ...

  6. java生成二维码,读取(解析)二维码图片

    二维码分为好多种,我们最常用的是qrcode类型的二维码,以下有三种生成方式以及解析方式: 附所需jar包或者js地址 第一种:依赖qrcode.jar import java.awt.Color; ...

  7. java关于Zxing 生成带Logo 二维码图片失真问题

    java关于Zxing 生成带Logo 二维码图片失真问题 问题点 logo本身是高清图片,但是Zxing生成的二维码中,logo像素失真,感觉被严重压缩一样. 排查问题 是Graphics2D 绘制 ...

  8. java生成二维码图片、转base64

    本文介绍通过java把文字或url生成二维码,使用浏览器或者微信扫一扫即可获得文字或url内容,超简单的方法,两个步骤复制粘贴即可使用. 注意:内容是文字会直接显示,如果内容为url地址那么会直接访问 ...

  9. Java生成二维码并以IO流的形式返回给前端展示(不需写入服务器),以及下载二维码图片

    目录 场景 方案分析 第一步--引入依赖 第二步--编写工具类 第三步--编写API接口 第四步--访问测试 第五步--下载图片 场景 最近笔者做的项目中,有一个需求: 在系统中生成一个二维码,用户保 ...

最新文章

  1. ux可以去哪些公司_忽略UX会如何伤害您的API以及您可以如何做
  2. 杀毒时能否使用计算机,电脑杀毒以后,程序无法使用,电脑杀毒后共享不能使用-...
  3. 2020 java swing jtable 合并_java学生管理系统(界面版)
  4. 你是一个有价值的产品经理吗?
  5. oracle crc32函数,CSS_ORACLE中实现CRC32的计算函数,SOLARIS平台,声明:这是我07年的一个偶然 - phpStudy...
  6. python布尔类型运算_python基础之布尔运算、集合
  7. 浅谈对程序员的认识_浅谈程序员的英语学习
  8. wordpress中文路径出现404错误的解决办法
  9. 2017年度工作总结
  10. jsp分页代码mysql_jsp分页(jsp分页完整代码)
  11. 如何选择嵌入式练手项目、嵌入式开源项目大全,嵌入式产品举例
  12. spring boot 动态代理
  13. ios设备的弹窗页面,光标错位,光标乱跳
  14. 1.firefox缺少flash插件
  15. 联邦学习:加密算法Paillier,Affine,IterativeAffine
  16. 多实例安装mysql数据库
  17. 好好说说互联网IT行业加班那点儿事
  18. vue 实现抽奖大转盘
  19. angular primeng table 非sortIcon排序
  20. 概率论抽球 模型 汇总

热门文章

  1. vin接口车架号vin查询车型
  2. julius开源语音识别引擎
  3. 出现这种错误 Failed to bind properties under ” to com.zaxxer.hikari.HikariDataSource:
  4. 3dsmax-拓扑插件Wrapit使用
  5. 算法系列之二十:计算中国农历(一)
  6. 字体的大小(pt)和像素(px)如何转换?
  7. 【正解】LaTex插入空白页
  8. 工业锅炉远程监控平台_工业物联网解决方案案例
  9. 【宇麦科技】群晖NAS套件之Drive的安装与使用(一),保姆级教程来喽!
  10. 电脑连接蓝牙耳机还是外放,输出设备只有扬声器怎么解决?