解决canvas转image/jpeg透明背景变黑问题

问题

png 图片转 jpeg 时,透明区域被填充成黑色

问题复现

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>

<body>
<p>Canvas:</p>
<canvas id="canvas" style="border: 1px solid #ccc;"></canvas>
<br />
<p>Base64转码后的图片:</p>
<div id="base64Img"></div>

<script type="text/javascript">
let base64Img = document.getElementById('base64Img'),
canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');

// 创建新图片
let img = new Image();
img.src = 'off.png';

img.addEventListener(
'load',
function () {
// 绘制图片到canvas上
canvas.width = img.width;
canvas.height = img.height;

ctx.drawImage(img, 0, 0);

getBase64(canvas, function (dataUrl) {
// 展示base64位的图片
const newImg = document.createElement('img');
newImg.src = dataUrl;

base64Img.appendChild(newImg);
});
},
false
);

// 获取canvas的base64图片的dataURL(图片格式为image/jpeg)
function getBase64(canvas, callback) {
const dataURL = canvas.toDataURL('image/jpeg');

if (typeof callback !== undefined) {
callback(dataURL);
}
}
</script>
</body>
</html>

问题原因

canvas 转为 jpeg 之前会移除 alpha 通道,所以透明区域被填充成了黑色。

期待效果

canvas 可以将 png 的透明区域填充成白色。

解决方案

1. 将透明的 pixel 设成白色

因为 png 图片的背景都是透明的,所以我们可以寻找透明的 pixel,然后将其全部设置成白色。

代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
// 将canvas的透明背景设置成白色
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data = imgData.data;
for (var i = 0; i < data.length; i += 4) {
if (data[i + 3] < 255) {
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
data[i + 3] = 255 - data[i + 3];
}
}
ctx.putImageData(imgData, 0, 0);

完整代码:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>

<body>
<p>Canvas:</p>
<canvas id="canvas" style="border: 1px solid #ccc;"></canvas>
<br />
<p>Base64转码后的图片:</p>
<div id="base64Img"></div>

<script type="text/javascript">
let base64Img = document.getElementById('base64Img'),
canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');

// 创建新图片
let img = new Image();
img.src = 'off.png';

img.addEventListener(
'load',
function () {
// 绘制图片到canvas上
canvas.width = img.width;
canvas.height = img.height;

ctx.drawImage(img, 0, 0);

// 将canvas的透明背景设置成白色
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data = imgData.data;
for (var i = 0; i < data.length; i += 4) {
if (data[i + 3] < 255) {
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
data[i + 3] = 255 - data[i + 3];
}
}
ctx.putImageData(imgData, 0, 0);

getBase64(canvas, function (dataUrl) {
// 展示base64位的图片
const newImg = document.createElement('img');
newImg.src = dataUrl;

base64Img.appendChild(newImg);
});
},
false
);

// 获取canvas的base64图片的dataURL(图片格式为image/jpeg)
function getBase64(canvas, callback) {
const dataURL = canvas.toDataURL('image/jpeg');

if (typeof callback !== undefined) {
callback(dataURL);
}
}
</script>
</body>
</html>

效果:

从图中可以看出这个方案是有缺陷的,当 png 图片上存在半透明区域时,也会将其填充为黑色。

解决方案来源:Change html canvas black background to white background when creating jpg image from png image

2. 在 canvas 绘制前填充白色背景

代码片段:

1
2
3
// 在canvas绘制前填充白色背景
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);

完整代码:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>

<body>
<p>Canvas:</p>
<canvas id="canvas" style="border: 1px solid #ccc;"></canvas>
<br />
<p>Base64转码后的图片:</p>
<div id="base64Img"></div>

<script type="text/javascript">
let base64Img = document.getElementById('base64Img'),
canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');

// 创建新图片
let img = new Image();
img.src = 'off.png';

img.addEventListener(
'load',
function () {
// 绘制图片到canvas上
canvas.width = img.width;
canvas.height = img.height;

// 在canvas绘制前填充白色背景
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);

ctx.drawImage(img, 0, 0);

getBase64(canvas, function (dataUrl) {
// 展示base64位的图片
const newImg = document.createElement('img');
newImg.src = dataUrl;

base64Img.appendChild(newImg);
});
},
false
);

// 获取canvas的base64图片的dataURL(图片格式为image/jpeg)
function getBase64(canvas, callback) {
const dataURL = canvas.toDataURL('image/jpeg');

if (typeof callback !== undefined) {
callback(dataURL);
}
}
</script>
</body>
</html>

效果:

显然,在 canvas 绘制前填充白色背景这种方法,不仅简单,而且对 png 图片的半透明区域填充难看的黑色块。推荐这种解决方案。

解决方案来源:How to fill the whole canvas with specific color