H5在移动端上传头像图片旋转90度问题解决方案

问题

移动端上的 H5 页面,在拍照上传后会出现图片自动旋转 90° 的问题,这里记录下解决方案。

解决思路

从图片 Exif 信息中读取 Orientation,根据它来判断是否该修正图片的方向:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//获取照片方向角属性,用户旋转控制
let Orientation = null;
EXIF.getData(image, function () {
Orientation = EXIF.getTag(this, 'Orientation');
});
switch (Orientation) {
case 1: // 不需要选择,正常
break;
case 6: // 需要顺时针(向左)90度旋转
break;
case 8: // 需要逆时针(向右)90度旋转
break;
case 3: // 需要180度旋转
break;
}

然后用 canvas 的 ctx.drawImage 对图片进行绘制,最后用 canvas.toDataURL 将画布转为 base64 格式数据的 DataURL 传给后端。

P.S. 什么是 EXIF

Exif(Exchangeable image file format)是专门为数码相机拍摄的照片定义的,可以记录数码照片的属性信息和拍摄数据。
Exif 可以附加于 JPEG、TIFF、RIFF 等文件之中,为其增加有关数码相机拍摄信息的内容和索引图或图像处理软件的版本信息。
Exif 信息是可以被任意编辑的,因此只有参考的功能。Exif 可能会泄露隐私信息,请选择图片查看其 Exif 信息。

具体代码

引入 exif.js

1
<script src="https://cdn.bootcss.com/exif-js/2.3.0/exif.min.js"></script>

uni-app 下创建上传头像组件:

yyt-upload-img.vue

1
2
3
4
5
6
7
8
9
10
11
<template>
<view class="yyt-upload-img"></view>
</template>

<script>
export { default } from './yyt-upload-img.js';
</script>

<style lang="less" scoped>
@import url('yyt-upload-img.less');
</style>

yyt-upload-img.js

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
export default {
name: 'yyt-upload-img',
// 注册属性
props: {
// 图片最大Size(兆M)
maxSize: {
type: Number,
default: 9,
required: false
},
// 图片最大宽度(px)
maxWidth: {
type: Number,
default: uni.upx2px(500),
required: false
},
getImgUrl: {
type: Function,
default: function () {}
// required: true
}
},
created() {},
data() {
return {
// 图片信息
picInfo: {
imgSize: '', // 图片大小M
picName: '', // 图片名称
url: '' // 图片地址
}
};
},
methods: {
// 开始选择图片
async _startChoose() {
// 选择图片
let [error, imgRes] = await uni.chooseImage({
count: 1
});

// 验证大小
let size = imgRes.tempFiles[0].size;
if (size / 1024000 > this.maxSize) {
this.$cw.showError(`图片不能大于${this.maxSize}M`);
return;
}

//文件路径
let filePath = imgRes.tempFiles[0].name;
// 临时文件列表
const tempFilePaths = imgRes.tempFiles;
// 获取最后一个.的位置
let index = filePath.lastIndexOf('.');
// 文件大小
this.picInfo.imgSize = (tempFilePaths[0].size / 1048576).toFixed(2);
this.picInfo.picName = tempFilePaths[0].name; //上传人文件描述 名字
//获取后缀
let ext = filePath.substr(index + 1);
if (['png', 'jpg', 'jpeg', 'bmp', 'gif'].indexOf(ext) == -1) {
uni.showToast({
title: '图片格式不正确',
icon: 'none'
});
return;
}

let baseStr = await this.uploadImg({
url: imgRes.tempFiles[0].path
});
console.log({
baseStr
});
if (baseStr) {
let [error2, res] = await uni.uploadFile({
url: getApp().globalData.PicDomain,
fileType: 'image',
filePath: baseStr,
name: 'file'
});
let fileData = JSON.parse(res.data);
this.picInfo.url = `${getApp().globalData.PicDomain}${fileData.path}`;
// 通知已拿到数据
this.$emit('getImgUrl', this.picInfo);
} else {
this.$cw.showError('上传失败,请重试');
this.$emit('getImgUrl', null);
}
},
// 图片上传
async uploadImg(opt) {
let maxWidth = uni.upx2px(this.maxWidth);
opt = opt || {};
opt.url = opt.url || '';
let Orientation = 1;
//获取图片META信息
let res = await this.getImageTag(opt.url, 'Orientation', function (e) {
console.log(e);
if (e != undefined) Orientation = e;
});
var img = null;
var canvas = null;
let obj = await this.loadingImage(opt.url);
if (obj) {
img = obj.img;
canvas = obj.canvas;

console.log(Orientation);
let baseStr = '';
//如果方向角不为1,都需要进行旋转
switch (Orientation) {
case 6: //需要顺时针(向右)90度旋转
console.log('(向右)90度旋转');
baseStr = this.rotateImg(img, 'right', canvas);
break;
case 8: //需要逆时针(向左)90度旋转
console.log('向左)90度旋转');
baseStr = this.rotateImg(img, 'left', canvas);
break;

case 3: //需要180度旋转 转两次
console.log('需要180度旋转');
baseStr = this.rotateImg(img, 'right', canvas, 2);
break;
default:
baseStr = this.rotateImg(img, '', canvas);
break;
}
return baseStr;
} else {
return null;
}
},
/**
* @description 加载图片
* @param {Object} imgSrc 图片地址
*/
async loadingImage(imgSrc) {
if (!imgSrc) return 0;
let [err, res] = await uni.getImageInfo({
src: imgSrc
});
return new Promise((resolve, reject) => {
if (res) {
let img = new Image();
img.style.opacity = 1;
img.style.backgroundColor = '#FFFFFF';
img.src = res.path;
img.width = res.width;
img.height = res.height;

img.onload = function () {
let canvas = document.createElement('canvas');
let obj = new Object();
obj.img = img;
obj.canvas = canvas;
resolve(obj);
};
} else {
that.$cw.showError('加载图片失败,请重试!');
reject(null);
}
});
},
/**
* @desc 获取图片信息,使用exif.js库,具体用法请在github中搜索
* @param {Object} file 上传的图片文件
* @param {String} tag 需要获取的信息 例如:'Orientation'旋转信息
* @return {Promise<Any>} 读取是个异步操作,返回指定的图片信息
*/
async getImageTag(file, tag, cb) {
if (!file) return 0;
let that = this;
return new Promise((resolve, reject) => {
let imgObj = new Image();
imgObj.src = file;

uni.getImageInfo({
src: file,
success(res) {
console.log({
res
});
EXIF.getData(imgObj, function () {
console.log(this);
EXIF.getAllTags(this);
let or = EXIF.getTag(this, 'Orientation'); //这个Orientation 就是我们判断需不需要旋转的值了,有1、3、6、8
resolve(cb(or));
});
},
fail(e) {
that.$cw.showError('加载图片失败,请换张图片重试!');
reject(null);
}
});
});
},
//网上提供的旋转function
rotateImg(img, direction, canvas, times = 1) {
console.log('开始旋转');
//最小与最大旋转方向,图片旋转4次后回到原方向
var min_step = 0;
var max_step = 3;
if (img == null) return;
//img的高度和宽度不能在img元素隐藏后获取,否则会出错
var height = img.height;
var width = img.width;
// debugger
let maxWidth = this.maxWidth;
let canvasWidth = width; //图片原始长宽
let canvasHeight = height;
let base = canvasWidth / canvasHeight;
if (canvasWidth > maxWidth) {
canvasWidth = maxWidth;
canvasHeight = Math.floor(canvasWidth / base);
}
width = canvasWidth;
height = canvasHeight;
var step = 0;

if (step == null) {
step = min_step;
}

if (direction == 'right') {
step += times;
//旋转到原位置,即超过最大值
step > max_step && (step = min_step);
} else if (direction == 'left') {
step -= times;
step < min_step && (step = max_step);
} else {
//不旋转
step = 0;
}

//旋转角度以弧度值为参数
var degree = (step * 90 * Math.PI) / 180;
var ctx = canvas.getContext('2d');
// 在canvas绘制前填充白色背景
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, this.maxWidth, this.maxWidth);

console.log(degree);
console.log(step);
switch (step) {
case 1:
console.log('右旋转 90度');
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, 0, -height, width, height);
break;
case 2:
console.log('旋转 180度');
canvas.width = width;
canvas.height = height;
ctx.rotate(degree);
ctx.drawImage(img, -width, -height, width, height);
break;
case 3:
console.log('左旋转 90度');
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, -width, 0, width, height);
break;
default:
//不旋转
canvas.width = width;
canvas.height = height;
console.log({
ctx,
canvas
});
ctx.drawImage(img, 0, 0, width, height);
break;
}

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);

let baseStr = canvas.toDataURL('image/png', 1);
return baseStr;
}
},
computed: {},
filters: {
// parseScene(value) {
// return value+'123';
// }
},
watch: {
// "currentStore.storeId": {
// handler(val, oldval) {
// if (val) {
// }
// }
// }
}
};

使用组件

1
2
3
<yyt-upload-img ref="uploadImg" @getImgUrl="getImgUrl"></yyt-upload-img>
// 点击上传头像按钮时调用 this.$refs.uploadImg._startChoose(); //
获取上传后的http图片地址 getImgUrl(imgUrl) { console.log(imgUrl); },