生成二维码

近来客户资料需要归档,用二维码来存储一些信息,为此特学习了二维码如何生成。

1. 需要的依赖如下

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

2. 二维码示例

可以加logo,也可以画上一些文字
示例

3. 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.UUID;
import javax.imageio.ImageIO;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import com.google.zxing.qrcode.encoder.Encoder;
import com.google.zxing.qrcode.encoder.QRCode;

public class QrCode {
static final int BLACK = 0xFF000000;
static final int WHITE = 0xFFFFFFFF;
static final int QUIET_ZONE_SIZE = 4;// 边框大小参数

public static void main(String[] args) throws IOException, WriterException {

String text = "C:\\springboot-tomcat-tmp\\upload\\QRCode\\20180119";
writeToStreamEntire(text, 500, 500, "", "", null);

}

public static void writeToStreamEntire(String text, int width, int height, String format, String newPath, Object o)
throws IOException, WriterException {

String p = "C:\\springboot-tomcat-tmp\\upload\\QRCode\\20180119";
//图片生成后的路径
newPath = p + File.separator + UUID.randomUUID() + ".jpg";
//预设置二维码大小 非实际值
width = 200;
height = 200;

System.out.println("writeToStreamEntire 入参: " + "text : " + text + " ,width=" + width + ",height=" + height
+ " ,format=" + format + ",newPath=" + newPath);
Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();
hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); // 内容所使用字符集编码
hints.put(EncodeHintType.ERROR_CORRECTION, "Q");// 容错率 共四种级别

BitMatrix bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints);
// 用户获取偏移 二维码 空白部分的偏移
Map<String, Integer> xy = getXY(text, BarcodeFormat.QR_CODE, width, height, hints);

// 二维码实际大小 包括边框
int widthR = bitMatrix.getWidth();
int heightR = bitMatrix.getHeight();

// 图片实际像素大小 实际的大小不能小于 二维码输出的大小 否则 扫不出来
int outHight = heightR + 500;

int outWidth = widthR + 500;

BufferedImage image = new BufferedImage(outWidth, outHight, BufferedImage.TYPE_INT_RGB);

for (int x = 0; x < outWidth; x++) {
for (int y = 0; y < outHight; y++) {
if (x < widthR && y < heightR) {
image.setRGB(x, y, bitMatrix.get(x, y) ? BLACK : WHITE);
} else {
image.setRGB(x, y, WHITE);
}
}
}
FileOutputStream stream = null;

// 中间加 个人图片

Graphics2D gs = image.createGraphics();

int ratioWidth = widthR * 2 / 10;
int ratioHeight = heightR * 2 / 10;
// 载入logo
String logoPath = "C:\\Users\\lixia\\Desktop\\java\\毕业季\\me.jpg";
Image img = ImageIO.read(new File(logoPath));
int logoWidth = img.getWidth(null) > ratioWidth ? ratioWidth : img.getWidth(null);
int logoHeight = img.getHeight(null) > ratioHeight ? ratioHeight : img.getHeight(null);

int x = (widthR - logoWidth) / 2;
int y = (heightR - logoHeight) / 2;

gs.drawImage(img, x, y, logoWidth, logoHeight, null);
gs.setColor(Color.black);
gs.setBackground(Color.WHITE);
gs.dispose();
img.flush();

try {
stream = new FileOutputStream(newPath);
if (!ImageIO.write(image, "jpg", stream)) {
throw new IOException("Could not write an image of format " + format);
}
// stream.write("四大地方地方".getBytes());
} finally {
if (stream != null) {
stream.close();
}
}
try {
// 读取原图片信息
File srcImgFile = new File(newPath);
Image srcImg = ImageIO.read(srcImgFile);
int srcImgWidth = srcImg.getWidth(null);
int srcImgHeight = srcImg.getHeight(null);

// 加水印 加工生成的图片
BufferedImage bufImg = new BufferedImage(srcImgWidth, srcImgHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g = bufImg.createGraphics();
// 将生成的图片 读取出来 花在新的 图片对象上 若宽和高于图片本身不一致,则进行缩放 画到新的图片对象中
g.drawImage(srcImg, 0, 0, widthR, heightR, null);

// 设置字体大小
// 根据最小字体大小设置
int min = widthR > heightR ? heightR : widthR;
int fontSize = (heightR - xy.get("Y") * 2) / 6;
Font font = new Font("宋体", Font.PLAIN, fontSize - fontSize * 2 / 10);
g.setColor(Color.white); // 根据图片的背景设置水印颜色
g.setFont(font);

// 设置右边偏移
int rightX = widthR + widthR * 2 / 10;

int rightY = xy.get("Y") + fontSize - fontSize * 2 / 10;

g.drawString("客户姓名:" + "李显春", rightX, rightY);
g.drawString("业务类型:" + "李显春", rightX, rightY + fontSize * 1);
g.drawString("品牌:" + "李显春", rightX, rightY + fontSize * 2);
g.drawString("车牌号:" + "李显春", rightX, rightY + fontSize * 3);
g.drawString("管理类型:" + "李显春", rightX, rightY + fontSize * 4);
g.drawString("档案管理:" + "李显春", rightX, rightY + fontSize * 5);

// 设置下边偏移
int downX = xy.get("X");
int downY = heightR + heightR * 2 / 10;
g.drawString("档案编号:" + "李显春", downX, downY);
g.drawString("合同号:" + "CPHZ20170504N000007", downX, downY + fontSize * 1);
g.drawString("车架号:" + "HT20170408CG0004", downX, downY + fontSize * 2);
g.drawString("资料清单:" + "归档资料(售后回租个人)", downX, downY + fontSize * 3);
g.drawString("经销商名称:" + "woshi", downX, downY + fontSize * 4);
g.dispose();
// 输出图片
FileOutputStream outImgStream = new FileOutputStream(newPath);
ImageIO.write(bufImg, "jpg", outImgStream);
outImgStream.flush();
outImgStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}

// 获取偏移
public static Map<String, Integer> getXY(String contents, BarcodeFormat format, int width, int height,
Map<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);

ByteMatrix input = code.getMatrix();
if (input == null) {
throw new IllegalStateException();
}
int inputWidth = input.getWidth();
int inputHeight = input.getHeight();
int qrWidth = inputWidth + (quietZone * 2);
int qrHeight = inputHeight + (quietZone * 2);
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;

Map<String, Integer> map = new HashMap<>();
map.put("X", leftPadding);
map.put("Y", topPadding);
return map;
}

}

4. 注意与总结

1 理论上来讲,二维码是限的,就从图片上的点数也可以发现,不过数量应该巨大,还不知道是如何编码解析的。
2 生成的二维码大小和边框是自动计算的,不能手动设置。
3 二维码是有容错率的,即使部分丢失,也是可以扫出来的,因为编码有冗余。