手机拍摄的照片在浏览器里被旋转


本文作者: jsweibo

本文链接: https://jsweibo.github.io/2019/01/10/%E6%89%8B%E6%9C%BA%E6%8B%8D%E6%91%84%E7%9A%84%E7%85%A7%E7%89%87%E5%9C%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E9%87%8C%E8%A2%AB%E6%97%8B%E8%BD%AC/

摘要

本文主要讲述了:

  1. 问题
  2. 原因
  3. 解决方案

正文

问题

使手机屏幕与地面保持垂直,打开照相机拍照,每拍完 1 张就将手机顺时针旋转 90 度,4 个方向各拍 1 张,共得 4 张照片。

打开相册,发现 4 张照片均显示正常。但在浏览器中,利用<img>元素读取 4 张照片,发现 4 张照片的旋转角度(顺时针)分别为270°90°180°

示例:

点击下载示例照片

解压密码:https://jsweibo.github.io/

localhost/index.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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
img {
width: 200px;
}
</style>
</head>
<body>
<img src="./img/iPhone 13.4.1/1.jpeg" alt="顺时针270°" />
<img src="./img/iPhone 13.4.1/2.jpeg" alt="顺时针0°" />
<img src="./img/iPhone 13.4.1/3.jpeg" alt="顺时针90°" />
<img src="./img/iPhone 13.4.1/4.jpeg" alt="顺时针180°" />
<hr />
<img src="./img/Redmi/1.jpg" alt="顺时针270°" />
<img src="./img/Redmi/2.jpg" alt="顺时针0°" />
<img src="./img/Redmi/3.jpg" alt="顺时针90°" />
<img src="./img/Redmi/4.jpg" alt="顺时针180°" />
<hr />
<script>
window.addEventListener('load', function () {
const foo = document.createElement('div');
foo.textContent = navigator.userAgent;
document.body.appendChild(foo);
document.body.appendChild(document.createElement('hr'));
});
</script>
</body>
</html>

原因

从前,相册应用会读取照片的 EXIF 信息中的Orientation属性来纠正方向,但<img>不会

截止至 2021-02-01,部分浏览器中的<img>也会读取照片的 EXIF 信息中的Orientation属性来纠正方向

System Browser Name Browser Version 是否会纠正
Windows 10 IE 11.0 不会
Windows 10 Firefox 76 不会
Windows 10 Firefox 84
Windows 10 Edg 81.0.416.68
Windows 10 Chrome 80.0.3987.149 不会
Windows 10 Chrome 81.0.4044.129
iOS Safari 13.1
Android EdgA 45.09.4.5079 不会
Android Firefox 68.0 不会
Android Firefox 85.0
Android MicroMessenger 8.0.1840 不会
Android QQ webview 8.5.5.5105 不会
Android MQQBrowser 10.3 不会
Android UCBrowser 12.9.7.1077 不会
Android 360 浏览器 9.0.0.140 不会
Android MiuiBrowser 11.0.10 不会

示例:获取照片的 EXIF 信息

1
2
3
#!/usr/bin/env bash

npm install exif-js

localhost/index.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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
img {
width: 200px;
}
</style>
<script defer src="./node_modules/exif-js/exif.js"></script>
</head>
<body>
<img src="./img/iPhone 13.4.1/1.jpeg" alt="顺时针270°" />
<img src="./img/iPhone 13.4.1/2.jpeg" alt="顺时针0°" />
<img src="./img/iPhone 13.4.1/3.jpeg" alt="顺时针90°" />
<img src="./img/iPhone 13.4.1/4.jpeg" alt="顺时针180°" />
<hr />
<img src="./img/Redmi/1.jpg" alt="顺时针270°" />
<img src="./img/Redmi/2.jpg" alt="顺时针0°" />
<img src="./img/Redmi/3.jpg" alt="顺时针90°" />
<img src="./img/Redmi/4.jpg" alt="顺时针180°" />
<hr />
<script>
function getEXIFInfo(element) {
return new Promise((resolve, reject) => {
EXIF.getData(element, function () {
resolve(EXIF.getAllTags(this));
});
});
}
window.addEventListener('load', function () {
const foo = document.createElement('div');
foo.textContent = navigator.userAgent;
document.body.appendChild(foo);
document.body.appendChild(document.createElement('hr'));

const imgList = document.querySelectorAll('img');
let imgIndex = 0;

getEXIFInfo(imgList[imgIndex])
.then((value) => {
console.log(value);
imgIndex++;
})
.then(() => getEXIFInfo(imgList[imgIndex]))
.then((value) => {
console.log(value);
imgIndex++;
})
.then(() => getEXIFInfo(imgList[imgIndex]))
.then((value) => {
console.log(value);
imgIndex++;
})
.then(() => getEXIFInfo(imgList[imgIndex]))
.then((value) => {
console.log(value);
imgIndex++;
})
.then(() => getEXIFInfo(imgList[imgIndex]))
.then((value) => {
console.log(value);
imgIndex++;
})
.then(() => getEXIFInfo(imgList[imgIndex]))
.then((value) => {
console.log(value);
imgIndex++;
})
.then(() => getEXIFInfo(imgList[imgIndex]))
.then((value) => {
console.log(value);
imgIndex++;
})
.then(() => getEXIFInfo(imgList[imgIndex]))
.then((value) => {
console.log(value);
imgIndex++;
});
});
</script>
</body>
</html>

解决方案

使用exif-js读取照片的Orientation属性,而后使用<canvas>旋转并重绘照片。

注意:如果浏览器已经会自动纠正方向,那么就不应该使用这个方法,否则重绘出来的照片反而会出问题。

示例:blob URL

localhost/index.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
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
img {
width: 200px;
}
</style>
<script defer src="./node_modules/exif-js/exif.js"></script>
</head>
<body>
<img src="./img/iPhone 13.4.1/1.jpeg" alt="顺时针270°" />
<img src="./img/iPhone 13.4.1/2.jpeg" alt="顺时针0°" />
<img src="./img/iPhone 13.4.1/3.jpeg" alt="顺时针90°" />
<img src="./img/iPhone 13.4.1/4.jpeg" alt="顺时针180°" />
<hr />
<img src="./img/Redmi/1.jpg" alt="顺时针270°" />
<img src="./img/Redmi/2.jpg" alt="顺时针0°" />
<img src="./img/Redmi/3.jpg" alt="顺时针90°" />
<img src="./img/Redmi/4.jpg" alt="顺时针180°" />
<hr />
<script>
function getImage(url, options) {
return new Promise((resolve, reject) => {
const tempImg = new Image();
tempImg.addEventListener('load', function () {
if (options && options.repairImageOrientation) {
// 需要纠正方向
EXIF.getData(this, function () {
const exifInfo = EXIF.getAllTags(this);

// 判断照片方向
switch (exifInfo.Orientation) {
case 3:
// 顺时针180°
// 继续顺时针旋转180°
{
const tempCanvas = document.createElement('canvas');
const tempCanvasCtx = tempCanvas.getContext('2d');
tempCanvas.width = tempImg.naturalWidth;
tempCanvas.height = tempImg.naturalHeight;
tempCanvasCtx.rotate((180 * Math.PI) / 180);
tempCanvasCtx.drawImage(
tempImg,
0,
0,
-tempCanvas.width,
-tempCanvas.height
);
tempCanvas.toBlob(function (blob) {
const outputImg = new Image();
outputImg.src = URL.createObjectURL(blob);
resolve(outputImg);
}, 'image/jpeg');
}
break;
case 6:
// 顺时针270°
// 继续顺时针旋转90°
{
const tempCanvas = document.createElement('canvas');
const tempCanvasCtx = tempCanvas.getContext('2d');
tempCanvas.width = tempImg.naturalHeight;
tempCanvas.height = tempImg.naturalWidth;
tempCanvasCtx.rotate((90 * Math.PI) / 180);
tempCanvasCtx.drawImage(
tempImg,
0,
0,
tempCanvas.height,
-tempCanvas.width
);
tempCanvas.toBlob(function (blob) {
const outputImg = new Image();
outputImg.src = URL.createObjectURL(blob);
resolve(outputImg);
}, 'image/jpeg');
}
break;
case 8:
// 顺时针90°
// 继续顺时针旋转270°
{
const tempCanvas = document.createElement('canvas');
const tempCanvasCtx = tempCanvas.getContext('2d');
tempCanvas.width = tempImg.naturalHeight;
tempCanvas.height = tempImg.naturalWidth;
tempCanvasCtx.rotate((270 * Math.PI) / 180);
tempCanvasCtx.drawImage(
tempImg,
0,
0,
-tempCanvas.height,
tempCanvas.width
);
tempCanvas.toBlob(function (blob) {
const outputImg = new Image();
outputImg.src = URL.createObjectURL(blob);
resolve(outputImg);
}, 'image/jpeg');
}
break;
default:
// 无需纠正方向,原样返回
resolve(tempImg);
break;
}
});
} else {
// 无需纠正方向,原样返回
resolve(tempImg);
}
});
tempImg.addEventListener('error', function (event) {
reject(event);
});
tempImg.src = url;
});
}

window.addEventListener('load', function () {
const foo = document.createElement('div');
foo.textContent = navigator.userAgent;
document.body.appendChild(foo);
document.body.appendChild(document.createElement('hr'));

const tempFragment = new DocumentFragment();

getImage('./img/iPhone 13.4.1/1.jpeg', {
repairImageOrientation: true,
})
.then((element) => {
tempFragment.appendChild(element);
})
.then(() =>
getImage('./img/iPhone 13.4.1/2.jpeg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
})
.then(() =>
getImage('./img/iPhone 13.4.1/3.jpeg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
})
.then(() =>
getImage('./img/iPhone 13.4.1/4.jpeg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
tempFragment.appendChild(document.createElement('hr'));
})
.then(() =>
getImage('./img/Redmi/1.jpg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
})
.then(() =>
getImage('./img/Redmi/2.jpg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
})
.then(() =>
getImage('./img/Redmi/3.jpg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
})
.then(() =>
getImage('./img/Redmi/4.jpg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
tempFragment.appendChild(document.createElement('hr'));

document.body.appendChild(tempFragment);
})
.catch((error) => {
console.log(error);
});
});
</script>
</body>
</html>

示例:data URL

localhost/index.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
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
img {
width: 200px;
}
</style>
<script defer src="./node_modules/exif-js/exif.js"></script>
</head>
<body>
<img src="./img/iPhone 13.4.1/1.jpeg" alt="顺时针270°" />
<img src="./img/iPhone 13.4.1/2.jpeg" alt="顺时针0°" />
<img src="./img/iPhone 13.4.1/3.jpeg" alt="顺时针90°" />
<img src="./img/iPhone 13.4.1/4.jpeg" alt="顺时针180°" />
<hr />
<img src="./img/Redmi/1.jpg" alt="顺时针270°" />
<img src="./img/Redmi/2.jpg" alt="顺时针0°" />
<img src="./img/Redmi/3.jpg" alt="顺时针90°" />
<img src="./img/Redmi/4.jpg" alt="顺时针180°" />
<hr />
<script>
function getImage(url, options) {
return new Promise((resolve, reject) => {
const tempImg = new Image();
tempImg.addEventListener('load', function () {
if (options && options.repairImageOrientation) {
// 需要纠正方向
EXIF.getData(this, function () {
const exifInfo = EXIF.getAllTags(this);

// 判断照片方向
switch (exifInfo.Orientation) {
case 3:
// 顺时针180°
// 继续顺时针旋转180°
{
const tempCanvas = document.createElement('canvas');
const tempCanvasCtx = tempCanvas.getContext('2d');
tempCanvas.width = tempImg.naturalWidth;
tempCanvas.height = tempImg.naturalHeight;
tempCanvasCtx.rotate((180 * Math.PI) / 180);
tempCanvasCtx.drawImage(
tempImg,
0,
0,
-tempCanvas.width,
-tempCanvas.height
);
const outputImg = new Image();
outputImg.src = tempCanvas.toDataURL('image/jpeg');
resolve(outputImg);
}
break;
case 6:
// 顺时针270°
// 继续顺时针旋转90°
{
const tempCanvas = document.createElement('canvas');
const tempCanvasCtx = tempCanvas.getContext('2d');
tempCanvas.width = tempImg.naturalHeight;
tempCanvas.height = tempImg.naturalWidth;
tempCanvasCtx.rotate((90 * Math.PI) / 180);
tempCanvasCtx.drawImage(
tempImg,
0,
0,
tempCanvas.height,
-tempCanvas.width
);
const outputImg = new Image();
outputImg.src = tempCanvas.toDataURL('image/jpeg');
resolve(outputImg);
}
break;
case 8:
// 顺时针90°
// 继续顺时针旋转270°
{
const tempCanvas = document.createElement('canvas');
const tempCanvasCtx = tempCanvas.getContext('2d');
tempCanvas.width = tempImg.naturalHeight;
tempCanvas.height = tempImg.naturalWidth;
tempCanvasCtx.rotate((270 * Math.PI) / 180);
tempCanvasCtx.drawImage(
tempImg,
0,
0,
-tempCanvas.height,
tempCanvas.width
);
const outputImg = new Image();
outputImg.src = tempCanvas.toDataURL('image/jpeg');
resolve(outputImg);
}
break;
default:
// 无需纠正方向,原样返回
resolve(tempImg);
break;
}
});
} else {
// 无需纠正方向,原样返回
resolve(tempImg);
}
});
tempImg.addEventListener('error', function (event) {
reject(event);
});
tempImg.src = url;
});
}

window.addEventListener('load', function () {
const foo = document.createElement('div');
foo.textContent = navigator.userAgent;
document.body.appendChild(foo);
document.body.appendChild(document.createElement('hr'));

const tempFragment = new DocumentFragment();

getImage('./img/iPhone 13.4.1/1.jpeg', {
repairImageOrientation: true,
})
.then((element) => {
tempFragment.appendChild(element);
})
.then(() =>
getImage('./img/iPhone 13.4.1/2.jpeg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
})
.then(() =>
getImage('./img/iPhone 13.4.1/3.jpeg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
})
.then(() =>
getImage('./img/iPhone 13.4.1/4.jpeg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
tempFragment.appendChild(document.createElement('hr'));
})
.then(() =>
getImage('./img/Redmi/1.jpg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
})
.then(() =>
getImage('./img/Redmi/2.jpg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
})
.then(() =>
getImage('./img/Redmi/3.jpg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
})
.then(() =>
getImage('./img/Redmi/4.jpg', {
repairImageOrientation: true,
})
)
.then((element) => {
tempFragment.appendChild(element);
tempFragment.appendChild(document.createElement('hr'));

document.body.appendChild(tempFragment);
})
.catch((error) => {
console.log(error);
});
});
</script>
</body>
</html>

参考资料

本文作者: jsweibo

本文链接: https://jsweibo.github.io/2019/01/10/%E6%89%8B%E6%9C%BA%E6%8B%8D%E6%91%84%E7%9A%84%E7%85%A7%E7%89%87%E5%9C%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E9%87%8C%E8%A2%AB%E6%97%8B%E8%BD%AC/


本文对你有帮助?请支持我


支付宝
微信