Java 中生成二维码(带 logo)

项目中涉及到生成二维码(像微信那种的),就用 ZXing 并混合 Java 的图形库搞了一个工具类。

奉上效果图一张
qrcode-1

废话不多说,直接上代码

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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import java.io.*;
import java.net.URL;
import java.util.*;

import javax.imageio.ImageIO;

import org.apache.commons.io.IOUtils;
import org.slf4j.*;

import com.google.zxing.*;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

/**
* 二维码生成工具类
*
* @author zhaoyibochn@gmail.com
*/
public class QRCodeUtils {

// logo 宽度
private static final int LOGO_WIDTH = 110;

private static final int LOGO_HEIGHT = LOGO_WIDTH;

// logo 边框宽度
private static final int LOGO_FRAME_WIDTH = 5;

// logo 边框颜色
private static final int LOGO_FRAME_COLOR = 0xffffffff;

// logo 边缘颜色
private static final int LOGO_BORDER_COLOR = 0x00000000;

// logo 圆角半径
private static final int LOGO_RADIUS = 10;

// 二维码前景色
private static final int QRCODE_FOREGROUND_COLOR = 0x00000000;

// 二维码背景色
private static final int QRCODE_BACKGROUND_COLOR = 0xffffffff;

// 二维码四周留白宽度
private static final int QRCODE_SPECIFIES_MARGIN = 1;

// 二维码写码器
private static final MultiFormatWriter mutiWriter = new MultiFormatWriter();

private static final Logger logger = LoggerFactory.getLogger(QRCodeUtils.class);

/**
* 将生成的二维码输出到流中
*
* @param content
* 二维码存储的信息
* @param width
* 二维码的宽度
* @param height
* 二维码的高度
* @param logo
* 输入流: Logo 源
* @param out
* 输出流
*/
public static void generateToStream(String content, int width, int height, InputStream logo,
OutputStream out) {
try {
ImageIO.write(genQRCode(content, width, height, logo), "jpg", out);
} catch (IOException e) {
e.printStackTrace();
logger.warn("Oops.", e);
} catch (WriterException e) {
e.printStackTrace();
logger.warn("Oops.", e);
}
}

/**
* 生成二维码
*
* @param content
* 二维码存储的信息
* @param width
* 二维码的宽度
* @param height
* 二维码的高度
* @param logo
* InputSteam of the logo
* @return {@link BufferedImage}
* @throws WriterException
* @throws IOException
*/
private static BufferedImage genQRCode(String content, int width, int height, InputStream logo)
throws WriterException, IOException {
int r = LOGO_RADIUS;

// 读取 logo 源图像
int logoPixels[][] = null;
boolean hasLogo = false;
if (logo != null) {
try {
logoPixels = getLogoPixels(scale(logo, LOGO_WIDTH, LOGO_HEIGHT, true), r);
hasLogo = true;
} catch (IOException e) {
e.printStackTrace();
logger.warn("Oops. Read the logo failed.", e);
}
}

Map<EncodeHintType, Object> hint = new HashMap<>();
hint.put(EncodeHintType.CHARACTER_SET, "utf-8");
hint.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hint.put(EncodeHintType.MARGIN, QRCODE_SPECIFIES_MARGIN);

// 生成二维码
BitMatrix matrix = mutiWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hint);

int foregroundColor = QRCODE_FOREGROUND_COLOR;
int backgroundColor = QRCODE_BACKGROUND_COLOR;

// 二维矩阵转为一维像素数组
int halfW = matrix.getWidth() / 2;
int halfH = matrix.getHeight() / 2;
int[] pixels = new int[width * height];
int logoHalfWidth = LOGO_WIDTH / 2;
int near = halfW - logoHalfWidth - LOGO_FRAME_WIDTH + r;
int far = halfW + logoHalfWidth + LOGO_FRAME_WIDTH - r;

for (int y = 0; y < matrix.getHeight(); y++) {
for (int x = 0; x < matrix.getWidth(); x++) {
if (!hasLogo) {
// 没有 logo 的二维码
pixels[y * width + x] = matrix.get(x, y) ? foregroundColor : backgroundColor;
} else {
// logo
if (x > halfW - logoHalfWidth && x < halfW + logoHalfWidth
&& y > halfH - logoHalfWidth && y < halfH + logoHalfWidth) {
pixels[y * width + x] = logoPixels[x - halfW + logoHalfWidth][y - halfH
+ logoHalfWidth];
}
// 在图片四周形成边框
else if ((x > halfW - logoHalfWidth - LOGO_FRAME_WIDTH
&& x < halfW - logoHalfWidth + LOGO_FRAME_WIDTH
&& y > halfH - logoHalfWidth - LOGO_FRAME_WIDTH
&& y < halfH + logoHalfWidth + LOGO_FRAME_WIDTH)
|| (x > halfW + logoHalfWidth - LOGO_FRAME_WIDTH
&& x < halfW + logoHalfWidth + LOGO_FRAME_WIDTH
&& y > halfH - logoHalfWidth - LOGO_FRAME_WIDTH
&& y < halfH + logoHalfWidth + LOGO_FRAME_WIDTH)
|| (x > halfW - logoHalfWidth - LOGO_FRAME_WIDTH
&& x < halfW + logoHalfWidth + LOGO_FRAME_WIDTH
&& y > halfH - logoHalfWidth - LOGO_FRAME_WIDTH
&& y < halfH - logoHalfWidth + LOGO_FRAME_WIDTH)
|| (x > halfW - logoHalfWidth - LOGO_FRAME_WIDTH
&& x < halfW + logoHalfWidth + LOGO_FRAME_WIDTH
&& y > halfH + logoHalfWidth - LOGO_FRAME_WIDTH
&& y < halfH + logoHalfWidth + LOGO_FRAME_WIDTH)) {

// 圆角处理
if (x < near && y < near
&& (near - x) * (near - x) + (near - y) * (near - y) > r * r) {
// 左上圆角
pixels[y * width + x] = matrix.get(x, y) ? foregroundColor
: backgroundColor;
} else if (x > far && y < near
&& (x - far) * (x - far) + (near - y) * (near - y) > r * r) {
// 右上圆角
pixels[y * width + x] = matrix.get(x, y) ? foregroundColor
: backgroundColor;
} else if (x < near && y > far
&& (near - x) * (near - x) + (y - far) * (y - far) > r * r) {
// 左下圆角
pixels[y * width + x] = matrix.get(x, y) ? foregroundColor
: backgroundColor;
} else if (x > far && y > far
&& (x - far) * (x - far) + (y - far) * (y - far) > r * r) {
// 右下圆角
pixels[y * width + x] = matrix.get(x, y) ? foregroundColor
: backgroundColor;
} else {
// 边框填充颜色
pixels[y * width + x] = backgroundColor;
}
} else {
pixels[y * width + x] = matrix.get(x, y) ? foregroundColor
: backgroundColor;
}
}

}
}

BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
image.getRaster().setDataElements(0, 0, width, height, pixels);

return image;
}

/**
* 对图片进行圆角处理, 并生成数组
*
* @param logo
* BufferedImage of the log
* @param radius
* 圆角半径
* @return logo 像素数组
*/
public static int[][] getLogoPixels(BufferedImage logo, int radius) {

int borderColor = LOGO_BORDER_COLOR;
int whiteColor = LOGO_FRAME_COLOR;

int srcPixels[][] = new int[LOGO_WIDTH][LOGO_HEIGHT];

int r = radius;// 圆角处理半径
int max = LOGO_WIDTH;

for (int x = 0; x < LOGO_WIDTH; x++) {
for (int y = 0; y < LOGO_HEIGHT; y++) {
if (x < r && y < r && ((r - x) * (r - x) + (r - y) * (r - y) > (r - 1) * (r - 1))) {
// 左上圆角
if ((r - x) * (r - x) + (r - y) * (r - y) > r * r) {
srcPixels[x][y] = whiteColor;
} else {
srcPixels[x][y] = borderColor;
}
} else if (x > (max - r) && y < r
&& (x + r - max) * (x + r - max) + (r - y) * (r - y) > (r - 1) * (r - 1)) {
// 右上圆角
if ((x + r - max) * (x + r - max) + (r - y) * (r - y) > r * r) {
srcPixels[x][y] = whiteColor;
} else {
srcPixels[x][y] = borderColor;
}
} else if (x < r && y > (max - r)
&& (r - x) * (r - x) + (y + r - max) * (y + r - max) > (r - 1) * (r - 1)) {
// 左下圆角
if ((r - x) * (r - x) + (y + r - max) * (y + r - max) > r * r) {
srcPixels[x][y] = whiteColor;
} else {
srcPixels[x][y] = borderColor;
}
} else if (x > (max - r) && y > (max - r) && (x + r - max) * (x + r - max)
+ (y + r - max) * (y + r - max) > (r - 1) * (r - 1)) {
// 右下圆角
if ((x + r - max) * (x + r - max) + (y + r - max) * (y + r - max) > r * r) {
srcPixels[x][y] = whiteColor;
} else {
srcPixels[x][y] = borderColor;
}
} else {
if (((x >= r && x <= max - r) && (y == 0 || y == 1 || y == max - 1 || y == max))
|| ((y >= r && y <= max - r)
&& (x == 0 || x == 1 || x == max - 1 || x == max))) {
// 四周除圆角的边框
srcPixels[x][y] = borderColor;
} else {
// 图片值
srcPixels[x][y] = logo.getRGB(x, y);
}
}
}
}
return srcPixels;
}

/**
* 把传入的原始图像按高度和宽度进行缩放,生成符合要求的图标
*
* @param src
* 源
* @param height
* 目标高度
* @param width
* 目标宽度
* @param hasFiller
* 比例不对时是否需要补白:true 为补白; false 为不补白;
* @return {@link BufferedImage}
* @throws IOException
*/
private static BufferedImage scale(InputStream src, int height, int width, boolean hasFiller)
throws IOException {
double ratio = 0.0D; // 缩放比例
BufferedImage srcImage = ImageIO.read(src);
Image destImage = srcImage.getScaledInstance(width, height, BufferedImage.SCALE_SMOOTH);
// 计算比例
if ((srcImage.getHeight() > height) || (srcImage.getWidth() > width)) {
if (srcImage.getHeight() > srcImage.getWidth()) {
ratio = (new Integer(height)).doubleValue() / srcImage.getHeight();
} else {
ratio = (new Integer(width)).doubleValue() / srcImage.getWidth();
}
AffineTransformOp op = new AffineTransformOp(
AffineTransform.getScaleInstance(ratio, ratio), null);
destImage = op.filter(srcImage, null);
}
// 补白
if (hasFiller) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphic = image.createGraphics();
graphic.setColor(Color.white);
graphic.fillRect(0, 0, width, height);
if (width == destImage.getWidth(null))
graphic.drawImage(destImage, 0, (height - destImage.getHeight(null)) / 2,
destImage.getWidth(null), destImage.getHeight(null), Color.white, null);
else graphic.drawImage(destImage, (width - destImage.getWidth(null)) / 2, 0,
destImage.getWidth(null), destImage.getHeight(null), Color.white, null);
graphic.dispose();
destImage = image;
}
return (BufferedImage) destImage;
}

public static void main(String[] args) {
InputStream in = null;
try {
in = new URL("https://dn-codingon.qbox.me/img/avatar/me.jpeg").openStream();
generateToStream("https://codingon.com", 500, 500, in,
new FileOutputStream("/Users/yibo/Desktop/qrcode.jpg"));
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(in);
}
}
}

依赖:

1
2
3
4
5
6
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.2.1</version>
<type>jar</type>
</dependency>

相关阅读:
二维码的生成细节和原理