Hello World

吞风吻雨葬落日 欺山赶海踏雪径

0%

Java HTML转图片

需求需要发送业务排名的钉钉消息,排名需要有一定的格式,又因为是动态生成,所以涉及到了HTML转图片的功能。目前调研下来Java有三种比较靠谱的方式

  • Java内置的 JEditorPane 结合 Graphics2D 渲染图片
  • html2image 三方库渲染图片
  • FlyingSaucer 三方库渲染图片
    本文就上面三种做测试比较。

结论

FlyingSaucer库的Graphics2DRenderer方式渲染效果最好,Java2DRenderer不支持背景图片。
html2image 设置的宽度一直不生效,效果中规中矩。
内置JEditorPane 渲染效果最差。

测试用HTML代码

测试代码(这里主要关注表格的渲染) htm2img.html:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"></meta>
<meta name="viewport" content="width=device-width, initial-scale=1.0"></meta>
<style>
body {
/* 背景图片的URL */
background-image: url('https://img.alicdn.com/imgextra/i4/O1CN0122Ph6S1FOVpfMvLxY_!!6000000000477-0-tps-850-360.jpg');
}
table {
margin: 50px auto;
border-collapse: collapse;
width: 80%;
}
table, th, td {
border: 1px solid rgb(235, 8, 8);
}
th, td {
padding: 10px;
text-align: center;
width: 80px;
}
th {
background-color: #e9f40d;
}
h1 {
/* 居中显示标题 */
text-align: center;
}
</style>
</head>
<body >
<h1>人员得分项目排名</h1>
<table>
<thead>
<tr>
<th>姓名</th>
<th>项目1</th>
<th>项目2</th>
<th>项目3</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice⭐</td>
<td>95</td>
<td>88</td>
<td>75</td>
</tr>
<tr>
<td>Bob<img src="https://img.alicdn.com/imgextra/i4/O1CN01g1Du7X1I5gOV2ZJhh_!!6000000000842-2-tps-18-14.png"></img></td>
<td>87</td>
<td>92</td>
<td>80</td>
</tr>
<tr>
<td>Bob2</td>
<td>87</td>
<td>92</td>
<td>80</td>
</tr>
<tr>
<td>Bob3</td>
<td>87</td>
<td>92</td>
<td>80</td>
</tr>
<tr>
<td>Bob4</td>
<td>87</td>
<td>92</td>
<td>80</td>
</tr>
<tr>
<td>Bob5</td>
<td>87</td>
<td>92</td>
<td>80</td>
</tr>
<tr>
<td>Bob6</td>
<td>87</td>
<td>92</td>
<td>80</td>
</tr>
<tr>
<td>Bob7</td>
<td>87</td>
<td>92</td>
<td>80</td>
</tr>
<tr>
<td>Bob8</td>
<td>87</td>
<td>92</td>
<td>80</td>
</tr>
</tbody>
</table>
</body>
</html>

Chrome 浏览器渲染效果
20231218000001.png

Java 内置库渲染方案

有点: 无需引用任何第三方库
缺点: 渲染效果不尽人意

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void testGraphics2D_native() throws IOException {
long start = System.currentTimeMillis();
File xhtml = ResourceUtils.getFile("classpath:file/htm2img.html");
String fileContent = FileUtils.readFileToString(xhtml);
int height = 800;
BufferedImage image = new BufferedImage(width, height, TYPE_INT_ARGB_PRE);
Graphics2D graphics = image.createGraphics();

//renderingHint为提升渲染图片质量的配置,有多个k,v
//下面的k,v代表启用抗锯齿
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

JEditorPane jep = new JEditorPane("text/html;charset=UTF-8", fileContent);
jep.setSize(width, height);
jep.print(graphics);

File output = new File(OUTPUT_PATH_2);
ImageIO.write(image, "png", output);
long end = System.currentTimeMillis();
System.out.println("cost: " + (end - start) + " ms");
}

渲染效果
渲染效果

html2image库

官网地址: https://github.com/hkirk/java-html2image

maven坐标(最后更新时间2020年)

1
2
3
4
5
<dependency>
<groupId>gui.ava</groupId>
<artifactId>html2image</artifactId>
<version>0.9</version>
</dependency>

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testHtml2image() throws Exception {

long start = System.currentTimeMillis();
File xhtml = ResourceUtils.getFile("classpath:file/htm2img.html");
String fileContent = FileUtils.readFileToString(xhtml);

HtmlImageGenerator generator = new HtmlImageGenerator();
generator.loadHtml(fileContent);
generator.setSize(new Dimension(width , 800));
File output = new File(OUTPUT_PATH_2);
generator.saveAsImage(output);
long end = System.currentTimeMillis();
System.out.println("cost: " + (end - start) + " ms");
}

渲染效果
渲染效果

FlyingSaucer

官网地址: https://github.com/flyingsaucerproject/flyingsaucer
maven坐标(持续有更新)

1
2
3
4
5
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-core</artifactId>
<version>9.3.1</version>
</dependency>

FlyingSaucer html转图片有两种方法

  1. Java2DRenderer
  2. Graphics2DRenderer

Java2DRenderer

Java2DRenderer测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testFlyingSaucerJava2DRenderer() throws Exception {

long start = System.currentTimeMillis();
File xhtml = ResourceUtils.getFile("classpath:file/htm2img.html");
String fileContent = FileUtils.readFileToString(xhtml);

XHTMLPanel xhtmlPanel = new XHTMLPanel();
xhtmlPanel.setDocumentFromString(fileContent, null, new XhtmlNamespaceHandler());
Java2DRenderer renderer = new Java2DRenderer(xhtmlPanel.getDocument(), 850, 1000);
BufferedImage image = renderer.getImage();
File output = new File(OUTPUT_PATH);
ImageIO.write(image, "png", output);

long end = System.currentTimeMillis();
System.out.println("cost: " + (end - start) + " ms");
}

渲染效果(不支持背景图片)
渲染效果

Graphics2DRenderer

Graphics2DRenderer测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testFlyingSaucerGraphics2DRenderer() throws Exception {
long start = System.currentTimeMillis();
File xhtml = ResourceUtils.getFile("classpath:file/htm2img.html");
String fileContent = FileUtils.readFileToString(xhtml);
XHTMLPanel xhtmlPanel = new XHTMLPanel();
xhtmlPanel.setDocumentFromString(fileContent, null, new XhtmlNamespaceHandler());
Graphics2DRenderer renderer = new Graphics2DRenderer(xhtmlPanel.getDocument(), 850, 1000);
BufferedImage image = renderer.getImage();
File output = new File(OUTPUT_PATH);
ImageIO.write(image, "png", output);
long end = System.currentTimeMillis();
System.out.println("cost: " + (end - start) + " ms");
}

渲染效果
渲染效果

因为大多情况下html代码片段是通过 模板 生成的(项目中使用了beetl),生成的结果是个String而非文件,使用Graphics2DRenderer也是支持的,需要额外写成:

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
@Test
public void testFlyingSaucerGraphics2DRenderer2() throws Exception {

long start = System.currentTimeMillis();
File xhtml = ResourceUtils.getFile("classpath:file/htm2img.html");
String fileContent = FileUtils.readFileToString(xhtml);

XHTMLPanel xhtmlPanel = new XHTMLPanel();
xhtmlPanel.setDocumentFromString(fileContent, null, new XhtmlNamespaceHandler());

Graphics2DRenderer g2r = new Graphics2DRenderer();
g2r.setDocument(xhtmlPanel.getDocument(), null);
Dimension dim = new Dimension(width, 1000);

// do layout with temp buffer
BufferedImage buff = new BufferedImage((int) dim.getWidth(), (int) dim.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) buff.getGraphics();
g2r.layout(g, new Dimension(width, 1000));
g.dispose();

// get size
Rectangle rect = g2r.getMinimumSize();

// render into real buffer
buff = new BufferedImage((int) rect.getWidth(), (int) rect.getHeight(), BufferedImage.TYPE_INT_ARGB);
g = (Graphics2D) buff.getGraphics();
g2r.render(g);
g.dispose();

// return real buffer
File output = new File(OUTPUT_PATH);
ImageIO.write(buff, "png", output);

long end = System.currentTimeMillis();
System.out.println("cost: " + (end - start) + " ms");
}