Java
二维码在目前的生活工作中,随处可见,日常开发中难免会遇到需要生成二维码的场景,网上也有很多开源的平台可以使用,不过这里可以通过几个开源组件,自己来实现一下。
在动手之前先思考一下需要进行的操作,首先需要生成一个二维码,其次需要在这里二维码中间添加一个头像。这里生成二维码使用工具 zxing,合成图片采用 thumbnailator,接下来实际操一下吧。

生成二维码

首先先根据目标地址,生成一个二维码,这里使用的是组件 zxing,在 SpringBoot 的pom依赖中,加入下面的依赖。

  1. <dependency>
  2. <groupId>com.google.zxing</groupId>
  3. <artifactId>core</artifactId>
  4. <version>3.4.0</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.google.zxing</groupId>
  8. <artifactId>javase</artifactId>
  9. <version>3.4.0</version>
  10. </dependency>

然后编写工具类 QRCodeGenerator.java

  1. import com.google.zxing.BarcodeFormat;
  2. import com.google.zxing.EncodeHintType;
  3. import com.google.zxing.MultiFormatWriter;
  4. import com.google.zxing.WriterException;
  5. import com.google.zxing.client.j2se.MatrixToImageWriter;
  6. import com.google.zxing.common.BitMatrix;
  7. import java.io.IOException;
  8. import java.nio.file.FileSystems;
  9. import java.nio.file.Path;
  10. import java.util.HashMap;
  11. public class QRCodeGenerator {
  12. /**
  13. * 根据内容,大小生成二维码到指定路径
  14. *
  15. * @param contents 跳转的链接
  16. * @param width 宽度
  17. * @param height 高度
  18. * @param filePath 路径
  19. * @throws WriterException
  20. * @throws IOException
  21. */
  22. public static void generateQrWithImage(String contents, int width, int height, String filePath) throws WriterException, IOException {
  23. //构造二维码写码器
  24. MultiFormatWriter mutiWriter = new com.google.zxing.MultiFormatWriter();
  25. HashMap<EncodeHintType, Object> hint = new HashMap<>(16);
  26. hint.put(EncodeHintType.CHARACTER_SET, "UTF-8");
  27. hint.put(EncodeHintType.ERROR_CORRECTION, com.google.zxing.qrcode.decoder.ErrorCorrectionLevel.H);
  28. hint.put(EncodeHintType.MARGIN, 1);
  29. //生成二维码
  30. BitMatrix bitMatrix = mutiWriter.encode(contents, BarcodeFormat.QR_CODE, width, height, hint);
  31. Path path = FileSystems.getDefault().getPath(filePath);
  32. MatrixToImageWriter.writeToPath(bitMatrix, "jpeg", path);
  33. }
  34. }

这个静态方法有四个参数,分别是:

  1. 跳转的链接内容;
  2. 二维码的宽度;
  3. 二维码的高度;
  4. 生成的二维码后的存放路径;

代码中还有几个常量,EncodeHintType.CHARACTER_SET:表示编码;EncodeHintType.ERROR_CORRECTION 表示二维码的容错率;EncodeHintType.MARGIN 表示二维码的边框。
解释一下什么是二维码的容错率,大家在日常生活或者工作中应该会发现,有些二维码轻轻一扫就扫成功了,有的二维码却很难扫成功,这背后就是二维码的容错率的原因(对,有时候并不是网络问题!)。

不同密度的二维码所包含的信息其编码的字符、容错率均不同。密度越低,编码的字符个数越少、容错率越低,二维码容错率表示二维码图标被遮挡多少后,仍可以被扫描出来的能力。目前,典型的二维码的容错率分为 7%、15%、25%、30% 四个等级,容错率越高,越容易被快速扫描。“但是,容错率越高,二维码里面的黑白格子也就越多。因此,对于目前主流手机,在绝大多数扫描场景下,仍普遍应用 7% 容错率的二维码就能满足需求。

感兴趣的小伙伴也可以自己尝试几个不同的容错率,看看扫码的难度有没有变化。
接下来,编写一个 main 方法来生成一个二维码

  1. public static void main(String[] args) {
  2. try {
  3. QRCodeGenerator.generateQrWithImage("https://www.yuque.com/fcant/java", 500, 500, "./QRCode.jpeg");
  4. } catch (Exception e) {
  5. }
  6. }

QRCode.jpeg
运行完上面的 main 方法,可以得到一个二维码,到这里第一步已经完成了,接下来就是给这个二维码加上头像。

添加头像

添加头像需要准备一个头像的照片,如果这里有现成大小的头像就直接拿来使用就行,如果没有也没有关系,可以自己裁剪,这里就需要用来图片处理工具 thumbnailator 了。
首先引入依赖

  1. <dependency>
  2. <groupId>net.coobird</groupId>
  3. <artifactId>thumbnailator</artifactId>
  4. <version>0.4.8</version>
  5. </dependency>

home_wallpaper_0.jpg
先将头像处理成 100 x 100 的大小,然后再合成上去就行,代码如下:

  1. public static void main(String[] args) {
  2. try {
  3. // 将大图片缩小到指定大小
  4. // ThumbnailsImageUtils.size("./photo.jpeg", 100, 100, 1, "./photo100.png");
  5. // 通过水印的形式,将头像加到生成的二维码上面
  6. ThumbnailsImageUtils.watermark("./QRCode.jpeg",
  7. 500, 500, Positions.CENTER, "./photo100.png",
  8. 1f, 1f, "./result.png");
  9. } catch (Exception e) {
  10. }
  11. }

result.png
这个就是最终生成的带头像的二维码了,通过上面的代码可以看到,ThumbnailsImageUtils 是真的很多强大,一行代码就能搞定图片缩小和图片合成,更多关于 ThumbnailsImageUtils 工具类的完整代码如下,赶紧收藏起来。

  1. import net.coobird.thumbnailator.Thumbnails;
  2. import net.coobird.thumbnailator.geometry.Positions;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import javax.imageio.ImageIO;
  6. import java.awt.*;
  7. import java.awt.image.BufferedImage;
  8. import java.io.File;
  9. import java.io.FileOutputStream;
  10. import java.io.IOException;
  11. import java.io.OutputStream;
  12. /**
  13. * <br>
  14. * <b>功能:</b><br>
  15. * <b>作者:</b>@author ziyou<br>
  16. * <b>日期:</b>2018-05-25 16:17<br>
  17. * <b>详细说明:</b>使用google开源工具Thumbnailator实现图片的一系列处理<br>
  18. */
  19. public class ThumbnailsImageUtils {
  20. private final static Logger logger = LoggerFactory.getLogger(ThumbnailsImageUtils.class);
  21. /**
  22. * 将原图根据指定大小生产新图
  23. *
  24. * @param sourceFilePath 原始图片路径
  25. * @param width 指定图片宽度
  26. * @param height 指定图片高度
  27. * @param targetFilePath 目标图片路径
  28. * @return 目标图片路径
  29. * @throws IOException
  30. */
  31. public static String thumb(String sourceFilePath, Integer width, Integer height, String targetFilePath) throws IOException {
  32. Thumbnails.of(sourceFilePath)
  33. .forceSize(width, height)
  34. .toFile(targetFilePath);
  35. return targetFilePath;
  36. }
  37. /**
  38. * 按照比例进行缩放
  39. *
  40. * @param sourceFilePath 原始图片路径
  41. * @param scale scale(比例)
  42. * @param targetFilePath 目标图片路径
  43. * @return 目标图片路径
  44. * @throws IOException
  45. */
  46. public static String scale(String sourceFilePath, Double scale, String targetFilePath) throws IOException {
  47. Thumbnails.of(sourceFilePath)
  48. .scale(scale)
  49. .toFile(targetFilePath);
  50. return targetFilePath;
  51. }
  52. /**
  53. * 不按照比例,指定大小进行缩放
  54. *
  55. * @param sourceFilePath 原始图片路径
  56. * @param width 指定图片宽度
  57. * @param height 指定图片高度
  58. * @param targetFilePath 目标图片路径
  59. * keepAspectRatio(false) 默认是按照比例缩放的
  60. * @return 目标图片路径
  61. * @throws IOException
  62. */
  63. public static String size(String sourceFilePath, Integer width, Integer height, float quality, String targetFilePath) throws IOException {
  64. Thumbnails.of(sourceFilePath)
  65. .size(width, height)
  66. .outputQuality(quality)
  67. .keepAspectRatio(false)
  68. .toFile(targetFilePath);
  69. return targetFilePath;
  70. }
  71. /**
  72. * 指定大小和角度旋转
  73. *
  74. * @param sourceFilePath 原始图片路径
  75. * @param width 指定图片宽度
  76. * @param height 指定图片高度
  77. * @param rotate rotate(角度),正数:顺时针 负数:逆时针
  78. * @param targetFilePath 目标图片路径
  79. * @return 目标图片路径
  80. * @throws IOException
  81. */
  82. public static String rotate(String sourceFilePath, Integer width, Integer height, Double rotate, String targetFilePath) throws IOException {
  83. Thumbnails.of(sourceFilePath)
  84. .size(width, height)
  85. .rotate(rotate)
  86. .toFile(targetFilePath);
  87. return targetFilePath;
  88. }
  89. /**
  90. * 指定角度旋转
  91. *
  92. * @param sourceFilePath 原始图片路径
  93. * @param rotate rotate(角度),正数:顺时针 负数:逆时针
  94. * @param targetFilePath 目标图片路径
  95. * @return 目标图片路径
  96. * @throws IOException
  97. */
  98. public static String rotate(String sourceFilePath, Double rotate, String targetFilePath) throws IOException {
  99. Thumbnails.of(sourceFilePath)
  100. .rotate(rotate)
  101. .toFile(targetFilePath);
  102. return targetFilePath;
  103. }
  104. /**
  105. * @param sourceFilePath 原始图片路径
  106. * @param width 指定图片宽度
  107. * @param height 指定图片高度
  108. * @param position 水印位置
  109. * @param waterFile 水印文件
  110. * @param opacity 水印透明度
  111. * @param quality 输出文件的质量
  112. * @param targetFilePath 目标图片路径
  113. * @return 目标图片路径
  114. * @throws IOException
  115. */
  116. public static String watermark(String sourceFilePath, Integer width, Integer height, Positions position, String waterFile, float opacity, float quality, String targetFilePath) throws IOException {
  117. Thumbnails.of(sourceFilePath)
  118. .size(width, height)
  119. .watermark(position, ImageIO.read(new File(waterFile)), opacity)
  120. .outputQuality(quality)
  121. .toFile(targetFilePath);
  122. return targetFilePath;
  123. }
  124. public static BufferedImage watermarkList(BufferedImage buffImg, int length, File[] waterFileArray) throws IOException {
  125. int x = 0;
  126. int y = 0;
  127. if (buffImg == null) {
  128. // 获取底图
  129. buffImg = new BufferedImage(1200, 1200, BufferedImage.SCALE_SMOOTH);
  130. } else {
  131. x = (length % 30) * 40;
  132. y = (length / 30) * 40;
  133. }
  134. // 创建Graphics2D对象,用在底图对象上绘图
  135. Graphics2D g2d = buffImg.createGraphics();
  136. // 将图像填充为白色
  137. g2d.setColor(Color.WHITE);
  138. g2d.fillRect(x, y, 1200, 40 * (waterFileArray.length + length));
  139. for (int i = 0; i < waterFileArray.length; i++) {
  140. // 获取层图
  141. BufferedImage waterImg = ImageIO.read(waterFileArray[i]);
  142. // 获取层图的宽度
  143. int waterImgWidth = waterImg.getWidth();
  144. // 获取层图的高度
  145. int waterImgHeight = waterImg.getHeight();
  146. // 在图形和图像中实现混合和透明效果
  147. g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1));
  148. // 绘制
  149. Integer j = i / 30;
  150. Integer index = i - j * 30;
  151. g2d.drawImage(waterImg, waterImgWidth * index + 1, waterImgHeight * j, waterImgWidth, waterImgHeight, null);
  152. }
  153. // 释放图形上下文使用的系统资源
  154. g2d.dispose();
  155. return buffImg;
  156. }
  157. /**
  158. * @param sourceFilePath 原始图片路径
  159. * @param waterFile 水印文件
  160. * @param targetFilePath 目标图片路径
  161. * 透明度默认值0.5f,质量默认0.8f
  162. * @return 目标图片路径
  163. * @throws IOException
  164. */
  165. public static String watermark(String sourceFilePath, String waterFile, String targetFilePath) throws IOException {
  166. Image image = ImageIO.read(new File(waterFile));
  167. Integer width = image.getWidth(null);
  168. Integer height = image.getHeight(null);
  169. return watermark(sourceFilePath, width, height,
  170. Positions.BOTTOM_RIGHT, waterFile, 0.5f, 0.8f, targetFilePath);
  171. }
  172. /**
  173. * 将图片转化为指定大小和格式的图片
  174. *
  175. * @param sourceFilePath
  176. * @param width
  177. * @param height
  178. * @param format
  179. * @param targetFilePath
  180. * @return
  181. * @throws IOException
  182. */
  183. public static String changeFormat(String sourceFilePath, Integer width, Integer height, String format, String targetFilePath) throws IOException {
  184. Thumbnails.of(sourceFilePath)
  185. .size(width, height)
  186. .outputQuality(0.8f)
  187. .outputFormat(format)
  188. .toFile(targetFilePath);
  189. return targetFilePath;
  190. }
  191. /**
  192. * 根据原大小转化指定格式
  193. *
  194. * @param sourceFilePath
  195. * @param format
  196. * @param targetFilePath
  197. * @return
  198. * @throws IOException
  199. */
  200. public static String changeFormat(String sourceFilePath, String format, String targetFilePath) throws IOException {
  201. Image image = ImageIO.read(new File(sourceFilePath));
  202. Integer width = image.getWidth(null);
  203. Integer height = image.getHeight(null);
  204. Thumbnails.of(sourceFilePath)
  205. .size(width, height)
  206. .outputFormat(format)
  207. .toFile(targetFilePath);
  208. return targetFilePath;
  209. }
  210. /**
  211. * 输出到输出流
  212. *
  213. * @param sourceFilePath
  214. * @param targetFilePath
  215. * @return
  216. * @throws IOException
  217. */
  218. public static String toOutputStream(String sourceFilePath, String targetFilePath) throws IOException {
  219. OutputStream os = new FileOutputStream(targetFilePath);
  220. Thumbnails.of(sourceFilePath).toOutputStream(os);
  221. return targetFilePath;
  222. }
  223. /**
  224. * 输出到BufferedImage
  225. *
  226. * @param sourceFilePath
  227. * @param format
  228. * @param targetFilePath
  229. * @return
  230. * @throws IOException
  231. */
  232. public static String asBufferedImage(String sourceFilePath, String format, String targetFilePath) throws IOException {
  233. /**
  234. * asBufferedImage() 返回BufferedImage
  235. */
  236. BufferedImage thumbnail = Thumbnails.of(sourceFilePath).size(1280, 1024).asBufferedImage();
  237. ImageIO.write(thumbnail, format, new File(targetFilePath));
  238. return targetFilePath;
  239. }
  240. }