当前位置:   article > 正文

Java生成二维码和处理白边的两种方式_java 生成二维码去除留白

java 生成二维码去除留白

一、生成二维码

1.1、使用hutool工具

参考资料:Hutool参考文档

maven引入依赖包及示例代码

  1. <dependency>
  2. <groupId>cn.hutool</groupId>
  3. <artifactId>hutool-all</artifactId>
  4. <version>5.7.4</version>
  5. </dependency>
  1. package io.renren.demo;
  2. import cn.hutool.core.io.FastByteArrayOutputStream;
  3. import cn.hutool.extra.qrcode.QrCodeUtil;
  4. import cn.hutool.extra.qrcode.QrConfig;
  5. import javax.imageio.ImageIO;
  6. import java.awt.*;
  7. import java.awt.image.BufferedImage;
  8. import java.io.IOException;
  9. import java.util.Base64;
  10. public class Demo14 {
  11. public static void main(String[] args) {
  12. // 二维码内容
  13. String url = "https://www.baidu.com/";
  14. QrConfig config = new QrConfig(300, 300);//二维码宽/高
  15. // 设置边距,既二维码和背景之间的边距(即白边大小)
  16. config.setMargin(2);
  17. // 设置前景色,既二维码颜色(青色)
  18. config.setForeColor(Color.CYAN.getRGB());
  19. // 设置背景色(灰色)
  20. config.setBackColor(Color.GRAY.getRGB());
  21. // 生成二维码并指定宽高
  22. BufferedImage generate = QrCodeUtil.generate(url, config);
  23. // 转换流信息写出
  24. FastByteArrayOutputStream os = new FastByteArrayOutputStream();
  25. try {
  26. ImageIO.write(generate, "png", os);
  27. } catch (IOException e) {
  28. e.getMessage();
  29. }
  30. //如果二维码要在前端显示需要转成Base64
  31. System.out.println("图片二维码:"+ Base64.getEncoder().encodeToString(os.toByteArray()));
  32. }
  33. }

1.2、使用zxing

maven引入依赖包:

  1. <!-- 二维码工具-->
  2. <dependency>
  3. <groupId>com.google.zxing</groupId>
  4. <artifactId>core</artifactId>
  5. <version>3.3.3</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>com.google.zxing</groupId>
  9. <artifactId>javase</artifactId>
  10. <version>3.3.3</version>
  11. </dependency>

推荐直接copy使用,功能比较齐全,因为我也是copy的

包结构:

 conf包下的QrCodeConfig.java文件

  1. import com.google.zxing.EncodeHintType;
  2. import com.google.zxing.WriterException;
  3. import com.google.zxing.client.j2se.MatrixToImageConfig;
  4. import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
  5. import com.mh.jishi.util.qrcode.QrCodeGenWrapper;
  6. import lombok.Getter;
  7. import lombok.Setter;
  8. import lombok.ToString;
  9. import java.awt.image.BufferedImage;
  10. import java.io.IOException;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. /**
  14. * 二维吗配置信息
  15. */
  16. @Getter
  17. @Setter
  18. @ToString
  19. public class QrCodeConfig {
  20. /**
  21. * 塞入二维码的信息
  22. */
  23. private String msg;
  24. /**
  25. * 二维码中间的logo
  26. */
  27. private String logo;
  28. /**
  29. * 生成二维码的宽
  30. */
  31. private Integer w;
  32. /**
  33. * 生成二维码的高
  34. */
  35. private Integer h;
  36. /**
  37. * 生成二维码的颜色
  38. */
  39. private MatrixToImageConfig matrixToImageConfig;
  40. private Map<EncodeHintType, Object> hints;
  41. /**
  42. * 生成二维码图片的格式 png, jpg
  43. */
  44. private String picType;
  45. @ToString
  46. public static class QrCodeConfigBuilder {
  47. private static final MatrixToImageConfig DEFAULT_CONFIG = new MatrixToImageConfig();
  48. /**
  49. * The message to put into QrCode
  50. */
  51. private String msg;
  52. /**
  53. * qrcode center logo
  54. */
  55. private String logo;
  56. /**
  57. * qrcode image width
  58. */
  59. private Integer w;
  60. /**
  61. * qrcode image height
  62. */
  63. private Integer h;
  64. /**
  65. * qrcode bgcolor, default white
  66. */
  67. private Integer offColor;
  68. /**
  69. * qrcode msg color, default black
  70. */
  71. private Integer onColor;
  72. /**
  73. * qrcode message's code, default UTF-8
  74. */
  75. private String code;
  76. /**
  77. * 0 - 4
  78. */
  79. private Integer padding;
  80. /**
  81. * error level, default H
  82. */
  83. private ErrorCorrectionLevel errorCorrection;
  84. /**
  85. * output qrcode image type, default png
  86. */
  87. private String picType;
  88. public String getMsg() {
  89. return msg;
  90. }
  91. public QrCodeConfigBuilder setMsg(String msg) {
  92. this.msg = msg;
  93. return this;
  94. }
  95. public String getLogo() {
  96. return logo;
  97. }
  98. public QrCodeConfigBuilder setLogo(String logo) {
  99. this.logo = logo;
  100. return this;
  101. }
  102. public Integer getW() {
  103. return w == null ? (h == null ? 200 : h) : w;
  104. }
  105. public QrCodeConfigBuilder setW(Integer w) {
  106. if (w != null && w < 0) {
  107. throw new IllegalArgumentException("???????????0");
  108. }
  109. this.w = w;
  110. return this;
  111. }
  112. public Integer getH() {
  113. if (w != null && w < 0) {
  114. throw new IllegalArgumentException("???????????0");
  115. }
  116. return h == null ? (w == null ? 200 : w) : h;
  117. }
  118. public QrCodeConfigBuilder setH(Integer h) {
  119. this.h = h;
  120. return this;
  121. }
  122. public Integer getOffColor() {
  123. return offColor == null ? MatrixToImageConfig.WHITE : offColor;
  124. }
  125. public QrCodeConfigBuilder setOffColor(Integer offColor) {
  126. this.offColor = offColor;
  127. return this;
  128. }
  129. public Integer getOnColor() {
  130. return onColor == null ? MatrixToImageConfig.BLACK : onColor;
  131. }
  132. public QrCodeConfigBuilder setOnColor(Integer onColor) {
  133. this.onColor = onColor;
  134. return this;
  135. }
  136. public String getCode() {
  137. return code == null ? "UTF-8" : code;
  138. }
  139. public QrCodeConfigBuilder setCode(String code) {
  140. this.code = code;
  141. return this;
  142. }
  143. public Integer getPadding() {
  144. if (padding == null) {
  145. return 1;
  146. }
  147. if (padding < 0) {
  148. return 0;
  149. }
  150. if (padding > 4) {
  151. return 4;
  152. }
  153. return padding;
  154. }
  155. public QrCodeConfigBuilder setPadding(Integer padding) {
  156. this.padding = padding;
  157. return this;
  158. }
  159. public String getPicType() {
  160. return picType == null ? "png" : picType;
  161. }
  162. public QrCodeConfigBuilder setPicType(String picType) {
  163. this.picType = picType;
  164. return this;
  165. }
  166. public ErrorCorrectionLevel getErrorCorrection() {
  167. return errorCorrection == null ? ErrorCorrectionLevel.H : errorCorrection;
  168. }
  169. public QrCodeConfigBuilder setErrorCorrection(ErrorCorrectionLevel errorCorrection) {
  170. this.errorCorrection = errorCorrection;
  171. return this;
  172. }
  173. private void validate() {
  174. if (msg == null || msg.length() == 0) {
  175. throw new IllegalArgumentException("????????????!");
  176. }
  177. }
  178. private QrCodeConfig create() {
  179. this.validate();
  180. QrCodeConfig qrCodeConfig = new QrCodeConfig();
  181. qrCodeConfig.setMsg(getMsg());
  182. qrCodeConfig.setH(getH());
  183. qrCodeConfig.setW(getW());
  184. qrCodeConfig.setLogo(getLogo());
  185. qrCodeConfig.setPicType(getPicType());
  186. Map<EncodeHintType, Object> hints = new HashMap<>(3);
  187. hints.put(EncodeHintType.ERROR_CORRECTION, this.getErrorCorrection());
  188. hints.put(EncodeHintType.CHARACTER_SET, this.getCode());
  189. hints.put(EncodeHintType.MARGIN, this.getPadding());
  190. qrCodeConfig.setHints(hints);
  191. MatrixToImageConfig config;
  192. if (getOnColor() == MatrixToImageConfig.BLACK
  193. && getOffColor() == MatrixToImageConfig.WHITE) {
  194. config = DEFAULT_CONFIG;
  195. } else {
  196. config = new MatrixToImageConfig(getOnColor(), getOffColor());
  197. }
  198. qrCodeConfig.setMatrixToImageConfig(config);
  199. return qrCodeConfig;
  200. }
  201. /**
  202. * create qrcodeConfig
  203. *
  204. * @return 返回构造的 QrCodeConfig 对象
  205. */
  206. public QrCodeConfig build() {
  207. return create();
  208. }
  209. public BufferedImage asBufferedImage() throws IOException, WriterException {
  210. return QrCodeGenWrapper.asBufferedImage(create());
  211. }
  212. public boolean asFile(String absFileName) throws IOException, WriterException {
  213. return QrCodeGenWrapper.asFile(create(), absFileName);
  214. }
  215. }
  216. }

util包:FileUtil.java

  1. import java.io.File;
  2. import java.io.FileNotFoundException;
  3. /**
  4. * 文件工具类
  5. */
  6. public class FileUtil {
  7. /**
  8. * 递归生成目录, file 为根据相对路径创建时, 会抛npe
  9. * @param file {@link File}file对象
  10. */
  11. public static void mkDir(File file) throws FileNotFoundException {
  12. if (file.getParentFile() == null) {
  13. file = file.getAbsoluteFile();
  14. }
  15. if (file.getParentFile().exists()) {
  16. if (!file.exists() && !file.mkdir()) {
  17. throw new FileNotFoundException();
  18. }
  19. } else {
  20. mkDir(file.getParentFile());
  21. if (!file.exists() && !file.mkdir()) {
  22. throw new FileNotFoundException();
  23. }
  24. }
  25. }
  26. /**
  27. * 创建文件
  28. *
  29. * @param filename 文件名称
  30. * @return {@link File} file对象
  31. */
  32. public static File createFile(String filename) throws FileNotFoundException {
  33. if (filename == null || "".equals(filename)) {
  34. return null;
  35. }
  36. int index = filename.lastIndexOf('/');
  37. if (index <= 0) {
  38. return new File(filename);
  39. }
  40. String path = filename.substring(0, index);
  41. mkDir(new File(path));
  42. return new File(filename);
  43. }
  44. }

图片工具类:ImageUtil.java

  1. import java.awt.*;
  2. import java.awt.geom.RoundRectangle2D;
  3. import java.awt.image.BufferedImage;
  4. import java.io.File;
  5. import java.io.IOException;
  6. import java.net.URL;
  7. /**
  8. * Image工具类
  9. */
  10. public class ImageUtil {
  11. /**
  12. * 在图片中间,插入圆角的logo
  13. *
  14. * @param qrCode 原图
  15. * @param logo logo地址
  16. */
  17. public static void insertLogo(BufferedImage qrCode, String logo) throws IOException {
  18. int QRCODE_WIDTH = qrCode.getWidth();
  19. int QRCODE_HEIGHT = qrCode.getHeight();
  20. // 获取logo图片
  21. BufferedImage bf = getImageByPath(logo);
  22. int size = bf.getWidth() > QRCODE_WIDTH * 2 / 10 ? QRCODE_WIDTH * 2 / 50 : bf.getWidth() / 5;
  23. bf = ImageUtil.makeRoundBorder(bf, 60, size, Color.WHITE); // 边距为二维码图片的1/10
  24. // logo的宽高
  25. int w = Math.min(bf.getWidth(), QRCODE_WIDTH * 2 / 10);
  26. int h = Math.min(bf.getHeight(), QRCODE_HEIGHT * 2 / 10);
  27. // 插入LOGO
  28. Graphics2D graph = qrCode.createGraphics();
  29. int x = (QRCODE_WIDTH - w) / 2;
  30. int y = (QRCODE_HEIGHT - h) / 2;
  31. graph.drawImage(bf, x, y, w, h, null);
  32. graph.dispose();
  33. bf.flush();
  34. }
  35. /**
  36. * 根据路径图片图片
  37. *
  38. * @param path 本地路径 or 网络地址
  39. * @return 图片
  40. */
  41. public static BufferedImage getImageByPath(String path) throws IOException {
  42. if (path.startsWith("http")) { // 从网络获取logo
  43. return ImageIO.read(new URL(path));
  44. } else if (path.startsWith("/")) { // 绝对地址获取logo
  45. return ImageIO.read(new File(path));
  46. } else { // 从资源目录下获取logo
  47. return ImageIO.read(new File(path));
  48. }
  49. }
  50. /**
  51. * 生成圆角图片 & 圆角边框
  52. *
  53. * @param image 原图
  54. * @param cornerRadius 圆角的角度
  55. * @param size 边框的边距
  56. * @param color 边框的颜色
  57. * @return 返回带边框的圆角图
  58. */
  59. public static BufferedImage makeRoundBorder(BufferedImage image, int cornerRadius, int size, Color color) {
  60. // 将图片变成圆角
  61. image = makeRoundedCorner(image, cornerRadius);
  62. int borderSize = size << 1;
  63. int w = image.getWidth() + borderSize;
  64. int h = image.getHeight() + borderSize;
  65. BufferedImage output = new BufferedImage(w, h,
  66. BufferedImage.TYPE_INT_ARGB);
  67. Graphics2D g2 = output.createGraphics();
  68. g2.setComposite(AlphaComposite.Src);
  69. g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
  70. RenderingHints.VALUE_ANTIALIAS_ON);
  71. g2.setColor(color == null ? Color.WHITE : color);
  72. g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius,
  73. cornerRadius));
  74. // ... then compositing the image on top,
  75. // using the white shape from above as alpha source
  76. g2.setComposite(AlphaComposite.SrcAtop);
  77. g2.drawImage(image, size, size, null);
  78. g2.dispose();
  79. return output;
  80. }
  81. /**
  82. * 生成圆角图片
  83. *
  84. * @param image 原始图片
  85. * @param cornerRadius 圆角的弧度
  86. * @return 返回圆角图
  87. */
  88. public static BufferedImage makeRoundedCorner(BufferedImage image,
  89. int cornerRadius) {
  90. int w = image.getWidth();
  91. int h = image.getHeight();
  92. BufferedImage output = new BufferedImage(w, h,
  93. BufferedImage.TYPE_INT_ARGB);
  94. Graphics2D g2 = output.createGraphics();
  95. // This is what we want, but it only does hard-clipping, i.e. aliasing
  96. // g2.setClip(new RoundRectangle2D ...)
  97. // so instead fake soft-clipping by first drawing the desired clip shape
  98. // in fully opaque white with antialiasing enabled...
  99. g2.setComposite(AlphaComposite.Src);
  100. g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
  101. RenderingHints.VALUE_ANTIALIAS_ON);
  102. g2.setColor(Color.WHITE);
  103. g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius,
  104. cornerRadius));
  105. // ... then compositing the image on top,
  106. // using the white shape from above as alpha source
  107. g2.setComposite(AlphaComposite.SrcAtop);
  108. g2.drawImage(image, 0, 0, null);
  109. g2.dispose();
  110. return output;
  111. }
  112. }

Matrix转Image工具类:MatrixToImageUtil.java

  1. package com.mh.jishi.util.qrcode.util;
  2. import com.google.zxing.common.BitMatrix;
  3. import com.mh.jishi.util.qrcode.conf.QrCodeConfig;
  4. import java.awt.*;
  5. import java.awt.image.BufferedImage;
  6. import java.io.IOException;
  7. /**
  8. * Matrix转工具类
  9. */
  10. public class MatrixToImageUtil {
  11. /**
  12. * 根据二维码配置 & 二维码矩阵生成二维码图片
  13. *
  14. * @param qrCodeConfig {@link QrCodeConfig}配置
  15. * @param bitMatrix {@link BitMatrix}
  16. * @return {@link BufferedImage}
  17. */
  18. public static BufferedImage toBufferedImage(QrCodeConfig qrCodeConfig, BitMatrix bitMatrix) throws IOException {
  19. int qrCodeWidth = bitMatrix.getWidth();
  20. int qrCodeHeight = bitMatrix.getHeight();
  21. BufferedImage qrCode = new BufferedImage(qrCodeWidth, qrCodeHeight, BufferedImage.TYPE_INT_RGB);
  22. for (int x = 0; x < qrCodeWidth; x++) {
  23. for (int y = 0; y < qrCodeHeight; y++) {
  24. qrCode.setRGB(x, y,
  25. bitMatrix.get(x, y) ?
  26. qrCodeConfig.getMatrixToImageConfig().getPixelOnColor() :
  27. qrCodeConfig.getMatrixToImageConfig().getPixelOffColor());
  28. }
  29. }
  30. // 插入logo
  31. if (!(qrCodeConfig.getLogo() == null || "".equals(qrCodeConfig.getLogo()))) {
  32. ImageUtil.insertLogo(qrCode, qrCodeConfig.getLogo());
  33. }
  34. // 若二维码的实际宽高和预期的宽高不一致, 则缩放
  35. int realQrCodeWidth = qrCodeConfig.getW();
  36. int realQrCodeHeight = qrCodeConfig.getH();
  37. if (qrCodeWidth != realQrCodeWidth || qrCodeHeight != realQrCodeHeight) {
  38. BufferedImage tmp = new BufferedImage(realQrCodeWidth, realQrCodeHeight, BufferedImage.TYPE_INT_RGB);
  39. tmp.getGraphics().drawImage(
  40. qrCode.getScaledInstance(realQrCodeWidth, realQrCodeHeight,
  41. Image.SCALE_SMOOTH), 0, 0, null);
  42. qrCode = tmp;
  43. }
  44. return qrCode;
  45. }
  46. }

主类:

QrCodeGenWrapper.java 生成工具类

  1. import cn.hutool.core.img.ImgUtil;
  2. import com.google.zxing.BarcodeFormat;
  3. import com.google.zxing.EncodeHintType;
  4. import com.google.zxing.WriterException;
  5. import com.google.zxing.common.BitMatrix;
  6. import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
  7. import com.google.zxing.qrcode.encoder.ByteMatrix;
  8. import com.google.zxing.qrcode.encoder.Encoder;
  9. import com.google.zxing.qrcode.encoder.QRCode;
  10. import com.mh.jishi.util.qrcode.conf.QrCodeConfig;
  11. import com.mh.jishi.util.qrcode.util.FileUtil;
  12. import com.mh.jishi.util.qrcode.util.MatrixToImageUtil;
  13. import org.apache.commons.lang3.StringUtils;
  14. import org.slf4j.Logger;
  15. import org.slf4j.LoggerFactory;
  16. import javax.imageio.ImageIO;
  17. import java.awt.image.BufferedImage;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.util.Map;
  21. /**
  22. * 对 zxing 的 QRCodeWriter 进行扩展, 解决白边过多的问题,原参考 <a href="https://my.oschina.net/u/566591/blog/872770">...</a>
  23. */
  24. public class QrCodeGenWrapper {
  25. private static final Logger logger = LoggerFactory.getLogger(QrCodeGenWrapper.class);
  26. private static final int QUIET_ZONE_SIZE = 4;
  27. /**
  28. * 构造 二维吗配置信息
  29. * @return QrCodeConfig
  30. */
  31. public static QrCodeConfig.QrCodeConfigBuilder createQrCodeConfig() {
  32. return new QrCodeConfig.QrCodeConfigBuilder();
  33. }
  34. /**
  35. * 生成base64格式二维吗
  36. * @param content 二维吗内容
  37. * @param width 宽度 默认 300
  38. * @param height 高度 默认 300
  39. * @param imageType 图片类型默认 ong
  40. * @return 返回base64格式二维码信息
  41. */
  42. public static String generateAsBase64(String content, Integer width, Integer height, String imageType) throws IOException, WriterException {
  43. QrCodeConfig qrConfig = QrCodeGenWrapper.createQrCodeConfig()
  44. .setMsg(content)
  45. .setH(width == null ? 300 : width)
  46. .setW(height == null ? 300 : height)
  47. .setPadding(1)
  48. .setErrorCorrection(ErrorCorrectionLevel.L)
  49. .build();
  50. BufferedImage img = asBufferedImage(qrConfig);
  51. return ImgUtil.toBase64DataUri(img, StringUtils.isBlank(imageType) ? "png" : imageType);
  52. }
  53. public static BufferedImage asBufferedImage(QrCodeConfig qrCodeConfig) throws WriterException, IOException {
  54. BitMatrix bitMatrix = encode(qrCodeConfig);
  55. return MatrixToImageUtil.toBufferedImage(qrCodeConfig, bitMatrix);
  56. }
  57. public static boolean asFile(QrCodeConfig qrCodeConfig, String absFileName) throws WriterException, IOException {
  58. File file = FileUtil.createFile(absFileName);
  59. if (file == null) {
  60. throw new IllegalArgumentException("file not exists! absFile: " + absFileName);
  61. }
  62. BufferedImage bufferedImage = asBufferedImage(qrCodeConfig);
  63. if (!ImageIO.write(bufferedImage, qrCodeConfig.getPicType(), file)) {
  64. throw new IOException("save qrcode image error!");
  65. }
  66. return true;
  67. }
  68. /**
  69. * 对 zxing 的 QRCodeWriter 进行扩展, 解决白边过多的问题
  70. * <p/>
  71. * 源码参考 {@link com.google.zxing.qrcode.QRCodeWriter#encode(String, BarcodeFormat, int, int, Map)}
  72. */
  73. private static BitMatrix encode(QrCodeConfig qrCodeConfig) throws WriterException {
  74. ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;
  75. int quietZone = 1;
  76. if (qrCodeConfig.getHints() != null) {
  77. if (qrCodeConfig.getHints().containsKey(EncodeHintType.ERROR_CORRECTION)) {
  78. errorCorrectionLevel = ErrorCorrectionLevel.valueOf(qrCodeConfig.getHints().get(EncodeHintType.ERROR_CORRECTION).toString());
  79. }
  80. if (qrCodeConfig.getHints().containsKey(EncodeHintType.MARGIN)) {
  81. quietZone = Integer.parseInt(qrCodeConfig.getHints().get(EncodeHintType.MARGIN).toString());
  82. }
  83. if (quietZone > QUIET_ZONE_SIZE) {
  84. quietZone = QUIET_ZONE_SIZE;
  85. } else if (quietZone < 0) {
  86. quietZone = 0;
  87. }
  88. }
  89. QRCode code = Encoder.encode(qrCodeConfig.getMsg(), errorCorrectionLevel, qrCodeConfig.getHints());
  90. return renderResult(code, qrCodeConfig.getW(), qrCodeConfig.getH(), quietZone);
  91. }
  92. /**
  93. * 对 zxing 的 QRCodeWriter 进行扩展, 解决白边过多的问题
  94. * <p/>
  95. * 源码参考
  96. *
  97. * @param code {@link QRCode}
  98. * @param width 高
  99. * @param height 宽
  100. * @param quietZone 取值 [0, 4]
  101. * @return {@link BitMatrix}
  102. */
  103. private static BitMatrix renderResult(QRCode code, int width, int height, int quietZone) {
  104. ByteMatrix input = code.getMatrix();
  105. if (input == null) {
  106. throw new IllegalStateException();
  107. }
  108. // xxx 二维码宽高相等, 即 qrWidth == qrHeight
  109. int inputWidth = input.getWidth();
  110. int inputHeight = input.getHeight();
  111. int qrWidth = inputWidth + (quietZone * 2);
  112. int qrHeight = inputHeight + (quietZone * 2);
  113. // 白边过多时, 缩放
  114. int minSize = Math.min(width, height);
  115. int scale = calculateScale(qrWidth, minSize);
  116. if (scale > 0) {
  117. if (logger.isDebugEnabled()) {
  118. logger.debug("qrCode scale enable! scale: {}, qrSize:{}, expectSize:{}x{}", scale, qrWidth, width, height);
  119. }
  120. int padding, tmpValue;
  121. // 计算边框留白
  122. padding = (minSize - qrWidth * scale) / QUIET_ZONE_SIZE * quietZone;
  123. tmpValue = qrWidth * scale + padding;
  124. if (width == height) {
  125. width = tmpValue;
  126. height = tmpValue;
  127. } else if (width > height) {
  128. width = width * tmpValue / height;
  129. height = tmpValue;
  130. } else {
  131. height = height * tmpValue / width;
  132. width = tmpValue;
  133. }
  134. }
  135. int outputWidth = Math.max(width, qrWidth);
  136. int outputHeight = Math.max(height, qrHeight);
  137. int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);
  138. int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
  139. int topPadding = (outputHeight - (inputHeight * multiple)) / 2;
  140. BitMatrix output = new BitMatrix(outputWidth, outputHeight);
  141. for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {
  142. // Write the contents of this row of the barcode
  143. for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
  144. if (input.get(inputX, inputY) == 1) {
  145. output.setRegion(outputX, outputY, multiple, multiple);
  146. }
  147. }
  148. }
  149. return output;
  150. }
  151. /**
  152. * 如果留白超过15% , 则需要缩放
  153. * (15% 可以根据实际需要进行修改)
  154. *
  155. * @param qrCodeSize 二维码大小
  156. * @param expectSize 期望输出大小
  157. * @return 返回缩放比例, <= 0 则表示不缩放, 否则指定缩放参数
  158. */
  159. private static int calculateScale(int qrCodeSize, int expectSize) {
  160. if (qrCodeSize >= expectSize) {
  161. return 0;
  162. }
  163. int scale = expectSize / qrCodeSize;
  164. int abs = expectSize - scale * qrCodeSize;
  165. if (abs < expectSize * 0.15) {
  166. return 0;
  167. }
  168. return scale;
  169. }
  170. }

测试:

  1. @Test
  2. public void qrCodeStrTest() throws IOException, WriterException {
  3. StringBuilder sb = new StringBuilder();
  4. for(int i = 0; i < 150; i ++){
  5. sb.append("12345678910").append("\r\n");
  6. }
  7. System.out.printf("二维码输出: %s%n \r 二维码内容字符数量: %s%n", QrCodeGenWrapper.generateAsBase64(sb.toString(), null, null, null), sb.length());
  8. System.out.printf("二维码输出: %s%n \r 二维码内容字符数量: %s%n", QrCodeGenWrapper.generateAsBase64("www.baidu.com", null, null, null), sb.length());
  9. }

附加条形码工具

BarCodeUtils .java 条形码工具类

  1. import com.google.zxing.BarcodeFormat;
  2. import com.google.zxing.EncodeHintType;
  3. import com.google.zxing.Writer;
  4. import com.google.zxing.WriterException;
  5. import com.google.zxing.client.j2se.MatrixToImageWriter;
  6. import com.google.zxing.common.BitMatrix;
  7. import com.google.zxing.oned.Code128Writer;
  8. import com.google.zxing.pdf417.PDF417Writer;
  9. import com.mh.jishi.constants.RedisKeyPrefix;
  10. import org.apache.commons.lang3.StringUtils;
  11. import javax.imageio.ImageIO;
  12. import java.awt.*;
  13. import java.awt.image.BufferedImage;
  14. import java.io.ByteArrayOutputStream;
  15. import java.io.IOException;
  16. import java.util.Base64;
  17. import java.util.HashMap;
  18. import java.util.Map;
  19. /**
  20. * <h2>条形码工具类</h2>
  21. * <p>
  22. * <a href="https://blog.51cto.com/u_15127597/4390771"> 原参考 </a>
  23. * </p>
  24. *
  25. * @author Evan <1922802352@qq.com>
  26. * @since 2022年06月09日 16:09
  27. */
  28. public class BarCodeUtils {
  29. /**
  30. * 默认图片宽度
  31. */
  32. private static final int DEFAULT_PICTURE_WIDTH = 270;
  33. /**
  34. * 默认图片高度
  35. */
  36. private static final int DEFAULT_PICTURE_HEIGHT = 130;
  37. /**
  38. * 默认条形码宽度
  39. */
  40. private static final int DEFAULT_BAR_CODE_WIDTH = 310;
  41. /**
  42. * 默认条形码高度
  43. */
  44. private static final int DEFAULT_BAR_CODE_HEIGHT = 80;
  45. /**
  46. * 默认字体大小
  47. */
  48. private static final int DEFAULT_FONT_SIZE = 15;
  49. /**
  50. * 设置 条形码参数
  51. */
  52. private static final Map<EncodeHintType, Object> hints = new HashMap<>();
  53. static {
  54. hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
  55. }
  56. /**
  57. * 获取条形码图片
  58. *
  59. * @param codeValue 条形码内容
  60. * @return 条形码图片
  61. */
  62. public static BufferedImage getBarCodeImage(String codeValue) {
  63. return getBarCodeImage(codeValue, DEFAULT_BAR_CODE_WIDTH, DEFAULT_BAR_CODE_HEIGHT);
  64. }
  65. /**
  66. * 获取条形码图片
  67. *
  68. * @param codeValue 条形码内容
  69. * @param width 宽度
  70. * @param height 高度
  71. * @return 条形码图片
  72. */
  73. public static BufferedImage getBarCodeImage(String codeValue, int width, int height) {
  74. // CODE_128是最常用的条形码格式
  75. return getBarCodeImage(codeValue, width, height, BarcodeFormat.CODE_128);
  76. }
  77. /**
  78. * 获取条形码图片
  79. *
  80. * @param codeValue 条形码内容
  81. * @param width 宽度
  82. * @param height 高度
  83. * @param barcodeFormat 条形码编码格式
  84. * @return 条形码图片
  85. */
  86. public static BufferedImage getBarCodeImage(String codeValue, int width, int height, BarcodeFormat barcodeFormat) {
  87. Writer writer;
  88. switch (barcodeFormat) {
  89. case PDF_417:
  90. // 支持中文的条形码格式
  91. writer = new PDF417Writer();
  92. break;
  93. case CODE_128:
  94. // 最常见的条形码,但是不支持中文
  95. // 如果使用到其他格式,可以在这里添加
  96. default:
  97. writer = new Code128Writer();
  98. }
  99. // 编码内容, 编码类型, 宽度, 高度, 设置参数
  100. BitMatrix bitMatrix;
  101. try {
  102. bitMatrix = writer.encode(codeValue, barcodeFormat, width, height, hints);
  103. } catch (WriterException e) {
  104. throw new RuntimeException("条形码内容写入失败");
  105. }
  106. return MatrixToImageWriter.toBufferedImage(bitMatrix);
  107. }
  108. /**
  109. * 获取条形码
  110. *
  111. * @param codeValue 条形码内容
  112. * @param bottomStr 底部文字
  113. * @return
  114. */
  115. public static BufferedImage getBarCodeWithWords(String codeValue, String bottomStr) {
  116. return getBarCodeWithWords(codeValue, bottomStr, "", "");
  117. }
  118. /**
  119. * 获取条形码
  120. *
  121. * @param codeValue 条形码内容
  122. * @param bottomStr 底部文字
  123. * @param topLeftStr 左上角文字
  124. * @param topRightStr 右上角文字
  125. * @return
  126. */
  127. public static BufferedImage getBarCodeWithWords(String codeValue, String bottomStr, String topLeftStr,
  128. String topRightStr) {
  129. return getCodeWithWords(getBarCodeImage(codeValue), bottomStr, null, topLeftStr, topRightStr,
  130. DEFAULT_PICTURE_WIDTH, DEFAULT_PICTURE_HEIGHT, 0, 0, 0, 0, 0, 0, DEFAULT_FONT_SIZE);
  131. }
  132. /**
  133. * 获取条形码,返回字符串base64图片
  134. *
  135. * @param codeValue 条形码内容
  136. * @param topStr 顶部文字
  137. * @param bottomStr 底部文字
  138. * @param topLeftStr 左上角文字
  139. * @param topRightStr 右上角文字
  140. * @return base64格式条形码图片
  141. */
  142. public static String getBarCodeWithWordsAsBase64(String codeValue, String topStr, String bottomStr,
  143. String topLeftStr, String topRightStr) throws IOException {
  144. String result;
  145. BufferedImage image = getCodeWithWords(getBarCodeImage(codeValue), bottomStr, topStr, topLeftStr, topRightStr,
  146. DEFAULT_PICTURE_WIDTH, DEFAULT_PICTURE_HEIGHT, 0, 0, 0, 0, 0, 0, DEFAULT_FONT_SIZE);
  147. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  148. ImageIO.write(image, "jpeg", outputStream);
  149. String base64 = Base64.getEncoder().encodeToString(outputStream.toByteArray());
  150. result = "data:image/jpeg;base64," + base64.replaceAll("\r\n", "");
  151. return result;
  152. }
  153. /**
  154. * 获取条形码,返回字符串base64图片, 可以缓存
  155. *
  156. * @param codeValue 条形码内容
  157. * @param bottomStr 底部文字
  158. * @param topLeftStr 左上角文字
  159. * @param topRightStr 右上角文字
  160. * @param cache 是否使用缓存
  161. */
  162. public static String getBarCodeWithWordsAsBase64ByCache(String codeValue, String topStr, String bottomStr,
  163. String topLeftStr, String topRightStr, boolean cache)
  164. throws IOException {
  165. String result = getBarCodeWithWordsAsBase64(codeValue, topStr, bottomStr, topLeftStr, topRightStr)
  166. return result;
  167. }
  168. /**
  169. * 获取条形码
  170. *
  171. * @param codeImage 条形码图片
  172. * @param bottomStr 底部文字
  173. * @param topStr 顶部文字
  174. * @param topLeftStr 左上角文字
  175. * @param topRightStr 右上角文字
  176. * @param pictureWidth 图片宽度
  177. * @param pictureHeight 图片高度
  178. * @param codeOffsetX 条形码宽度
  179. * @param codeOffsetY 条形码高度
  180. * @param topLeftOffsetX 左上角文字X轴偏移量
  181. * @param topLeftOffsetY 左上角文字Y轴偏移量
  182. * @param topRightOffsetX 右上角文字X轴偏移量
  183. * @param topRightOffsetY 右上角文字Y轴偏移量
  184. * @param fontSize 字体大小
  185. * @return 条形码图片
  186. */
  187. public static BufferedImage getCodeWithWords(BufferedImage codeImage, String bottomStr, String topStr,
  188. String topLeftStr, String topRightStr, int pictureWidth,
  189. int pictureHeight, int codeOffsetX,
  190. int codeOffsetY, int topLeftOffsetX, int topLeftOffsetY,
  191. int topRightOffsetX, int topRightOffsetY,
  192. int fontSize) {
  193. BufferedImage picImage = new BufferedImage(pictureWidth, pictureHeight, BufferedImage.TYPE_INT_RGB);
  194. Graphics2D g2d = picImage.createGraphics();
  195. // 抗锯齿
  196. setGraphics2D(g2d);
  197. // 设置白色
  198. setColorWhite(g2d, picImage.getWidth(), picImage.getHeight());
  199. // 条形码默认居中显示
  200. int codeStartX = (pictureWidth - codeImage.getWidth()) / 2 + codeOffsetX;
  201. int codeStartY = (pictureHeight - codeImage.getHeight()) / 2 + codeOffsetY;
  202. // 画条形码到新的面板
  203. g2d.drawImage(codeImage, codeStartX, codeStartY, codeImage.getWidth(), codeImage.getHeight(), null);
  204. // 画文字到新的面板
  205. g2d.setColor(Color.BLACK);
  206. // 字体、字型、字号
  207. g2d.setFont(new Font("微软雅黑", Font.PLAIN, fontSize));
  208. // 文字与条形码之间的间隔
  209. int wordAndCodeSpacing = 3;
  210. if (StringUtils.isNotEmpty(bottomStr)) {
  211. // 文字长度
  212. int strWidth = g2d.getFontMetrics().stringWidth(bottomStr);
  213. // 文字X轴开始坐标,这里是居中
  214. int strStartX = codeStartX + (codeImage.getWidth() - strWidth) / 2;
  215. // 文字Y轴开始坐标
  216. int strStartY = codeStartY + codeImage.getHeight() + fontSize + wordAndCodeSpacing;
  217. // 画文字
  218. g2d.drawString(bottomStr, strStartX, strStartY);
  219. }
  220. if (StringUtils.isNotEmpty(topStr)) {
  221. // 文字长度
  222. int strWidth = g2d.getFontMetrics().stringWidth(topStr);
  223. // 文字X轴开始坐标,这里是居中
  224. int strStartX = codeStartX + (codeImage.getWidth() - strWidth) / 2;
  225. // 文字Y轴开始坐标
  226. int strStartY = codeStartY + topLeftOffsetY - wordAndCodeSpacing;
  227. // 画文字
  228. g2d.drawString(topStr, strStartX, strStartY);
  229. }
  230. if (StringUtils.isNotEmpty(topLeftStr)) {
  231. // 文字长度
  232. int strWidth = g2d.getFontMetrics().stringWidth(topLeftStr);
  233. // 文字X轴开始坐标
  234. int strStartX = codeStartX + topLeftOffsetX;
  235. // 文字Y轴开始坐标
  236. int strStartY = codeStartY + topLeftOffsetY - wordAndCodeSpacing;
  237. // 画文字
  238. g2d.drawString(topLeftStr, strStartX, strStartY);
  239. }
  240. if (StringUtils.isNotEmpty(topRightStr)) {
  241. // 文字长度
  242. int strWidth = g2d.getFontMetrics().stringWidth(topRightStr);
  243. // 文字X轴开始坐标,这里是居中
  244. int strStartX = codeStartX + codeImage.getWidth() - strWidth + topRightOffsetX;
  245. // 文字Y轴开始坐标
  246. int strStartY = codeStartY + topRightOffsetY - wordAndCodeSpacing;
  247. // 画文字
  248. g2d.drawString(topRightStr, strStartX, strStartY);
  249. }
  250. g2d.dispose();
  251. picImage.flush();
  252. return picImage;
  253. }
  254. /**
  255. * 设置 Graphics2D 属性 (抗锯齿)
  256. *
  257. * @param g2d Graphics2D提供对几何形状、坐标转换、颜色管理和文本布局更为复杂的控制
  258. */
  259. private static void setGraphics2D(Graphics2D g2d) {
  260. g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  261. g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT);
  262. Stroke s = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
  263. g2d.setStroke(s);
  264. }
  265. /**
  266. * 设置背景为白色
  267. *
  268. * @param g2d Graphics2D提供对几何形状、坐标转换、颜色管理和文本布局更为复杂的控制
  269. */
  270. private static void setColorWhite(Graphics2D g2d, int width, int height) {
  271. g2d.setColor(Color.WHITE);
  272. // 填充整个屏幕
  273. g2d.fillRect(0, 0, width, height);
  274. // 设置笔刷
  275. g2d.setColor(Color.BLACK);
  276. }
  277. }

测试:

  1. @Test
  2. public void test1() throws IOException {
  3. String base64Image = BarCodeUtils.getBarCodeWithWordsAsBase64("1231213", "我是卧的那个龙", "CF 墨心", null, null);
  4. System.out.println(base64Image);
  5. }

二、解决白边办法

1、使用上面的缩放办法:参考资料:zxing 二维码大白边一步一步修复指南 - 小灰灰Blog的个人空间 - OSCHINA - 中文开源技术交流社区

2、想办法将二维码的背景色设置成透明色(什么,你对背景色有要求,请采用第一个办法)

  1. package io.renren.utils;
  2. import javax.imageio.ImageIO;
  3. import javax.swing.*;
  4. import java.awt.*;
  5. import java.awt.image.BufferedImage;
  6. import java.io.ByteArrayInputStream;
  7. import java.io.ByteArrayOutputStream;
  8. import java.io.IOException;
  9. public class ImagebackColorUtil {
  10. /**
  11. * 将背景替换为透明
  12. *
  13. * @param imgBytes the img bytes
  14. * @return
  15. * @throws IOException the io exception
  16. * @author Jack Que
  17. * @created 2021 -07-08 10:25:10 Change img color.
  18. */
  19. public static byte[] changeImgColor(byte[] imgBytes) throws IOException {
  20. BufferedImage bi = ImageIO.read(new ByteArrayInputStream(imgBytes));
  21. Image image = (Image) bi;
  22. //将原图片的二进制转化为ImageIcon
  23. ImageIcon imageIcon = new ImageIcon(image);
  24. int width = imageIcon.getIconWidth();
  25. int height = imageIcon.getIconHeight();
  26. //
  27. //图片缓冲流
  28. BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
  29. Graphics2D graphics2D = (Graphics2D) bufferedImage.getGraphics();
  30. graphics2D.drawImage(imageIcon.getImage(), 0, 0, imageIcon.getImageObserver());
  31. int alpha = 255;
  32. //这个背景底色的选择,我这里选择的是比较偏的位置,可以修改位置。背景色选择不知道有没有别的更优的方式(比如先过滤一遍获取颜色次数最多的,但是因为感觉做起来会比较复杂没去实现),如果有可以评论。
  33. int RGB=bufferedImage.getRGB(width-1, height-1);
  34. for(int i = bufferedImage.getMinX(); i < width; i++) {
  35. for(int j = bufferedImage.getMinY(); j < height; j++) {
  36. int rgb = bufferedImage.getRGB(i, j);
  37. int r = (rgb & 0xff0000) >>16;
  38. int g = (rgb & 0xff00) >> 8;
  39. int b = (rgb & 0xff);
  40. int R = (RGB & 0xff0000) >>16;
  41. int G = (RGB & 0xff00) >> 8;
  42. int B = (RGB & 0xff);
  43. //a为色差范围值,渐变色边缘处理,数值需要具体测试,50左右的效果比较可以
  44. int a = 45;
  45. if(Math.abs(R-r) < a && Math.abs(G-g) < a && Math.abs(B-b) < a ) {
  46. alpha = 0;
  47. } else {
  48. alpha = 255;
  49. }
  50. rgb = (alpha << 24)|(rgb & 0x00ffffff);
  51. bufferedImage.setRGB(i,j,rgb);
  52. }
  53. }
  54. // graphics2D.drawImage(bufferedImage, 0, 0, imageIcon.getImageObserver());
  55. //新建字节输出流,用来存放替换完背景的图片
  56. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  57. ImageIO.write(bufferedImage, "png", byteArrayOutputStream);
  58. return byteArrayOutputStream.toByteArray();
  59. // String[] split = fileName.split("\\.");
  60. // fileName = split[0]+"(已转换)."+split[1];
  61. // ImageIO.write(bufferedImage, "png", new File("D:\\"+fileName));
  62. }
  63. public static String convertRgbStr(int color) {
  64. int red = (color & 0xff0000) >> 16;// 获取color(RGB)中R位
  65. int green = (color & 0x00ff00) >> 8;// 获取color(RGB)中G位
  66. int blue = (color & 0x0000ff);// 获取color(RGB)中B位
  67. return red + "," + green + "," + blue;
  68. }
  69. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/604380
推荐阅读
相关标签
  

闽ICP备14008679号