移动端图片上传旋转问题

前言

最近在处理移动端选择图片实时预览并上传时遇到一个问题:上传前图片使用img标签预览正常,但通过canvas对图片进行裁切处理,有时会出现图片翻转的问题,一般是翻转 90 度。后经一翻测试研究,发现横向拍照,正常,竖直拍照图片出现旋转90度,后来发现图片拍照会有 Exif 信息存在方向问题。

PS:其实,大部分的图片查看客户端早已支持自动旋转,所以一般情况下数码设备拍的照片用电脑看,方向都是正确的。许多缩略图生成程序,也是可以指定缩放前自动旋转的(例如 ImageMagick 的 -auto-orient 参数)。

EXIF

EXIF 全称 (Exchangeable Image File Format) 照相机拍摄图像一般都由两大部分组成,一部分是数据本身,它记录了每个像素的颜色值,另外一部分是文件头,这里面记录着形如图像的宽度,高度等信息。所讨论的方向信息便是被存储于文件头中。更为具体一些:百度百科中对其 EXIF 的解释为:

可交换图像文件格式常被简称为Exif(Exchangeable image file format),是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据… Exif可以附加于JPEG、TIFF、RIFF等文件之中。

注意:PNG格式的图像中不包含。

Exif 信息

当然,Exif 中的 Orientation 属性,取决于拍照的设备是否拥有方向传感器。不过根据我的了解,目前大部分数码拍照设备都支持记录方向。1 是默认值,2、4、5、7 表示照片进行了翻转。一般情况下,取值应该是 1、3、6、8 中的一种。下面有张更形象的图描述了具体的旋转策略:

Exif 信息

获取EXIF旋转图片

如何从图片中获取 Exif 信息,各个语言都有封装好的代码可以直接使用。推荐一个比较好用的库,api也比较齐全 exif-js,它做法分为两步,首先判断 img.src,如果是 Data URI(即base64)则通过 atob 函数转解码base-64编码字符串换为二进制,如果是 Object URL 通过 readAsArrayBuffer 函数读取二进制,反之则通过 Ajax 获取原始二进制;第二步是从原始数据不同位置匹配获取相关信息,基本是体力活了。

从图片 Exif 信息中取到 Orientation 后,就可以根据它来自动旋转图片了,示例代码:

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
input.addEventListener("change", function(){
var file = this.files[0];

// 接受 jpeg, jpg, png 类型的图片
// if (!/\/(?:jpeg|jpg|png)/i.test(file.type)) return;

var reader = new FileReader();
var compressCanvas = document.querySelector(".babyhappy_compressimg_con");
compressCtx = compressCanvas.getContext("2d");

reader.onload = function(event) {
var result = this.result;
var image = new Image();
image.src = reader.result;

image.onload = function(){
EXIF.getData(image, function(){
EXIF.getAllTags(this);
var orientation = EXIF.getTag(this, 'Orientation');
switch(orientation){
case 1:
//0°
compressCanvas.height = image.height;
compressCanvas.width = image.width;
compressCtx.clearRect(0, 0, compressCanvas.width, compressCanvas.height)
compressCtx.drawImage(image, 0, 0, image.width, image.height);
break;
case 6:
//顺时针90°
compressCanvas.width = image.height;
compressCanvas.height = image.width;
compressCtx.clearRect(0, 0, compressCanvas.width, compressCanvas.height)
compressCtx.translate(0, 0);
compressCtx.rotate(90 * Math.PI / 180);
compressCtx.drawImage(image, 0, -image.height, image.width, image.height);
break;
case 8:
//逆时针90°
compressCanvas.width = image.height;
compressCanvas.height = image.width;
compressCtx.clearRect(0, 0, compressCanvas.width, compressCanvas.height)
compressCtx.translate(0, 0);
compressCtx.rotate(-90 * Math.PI / 180);
compressCtx.drawImage(image, -image.width, 0, image.width, image.height);
break;
case 3:
//180°
compressCanvas.height = image.height;
compressCanvas.width = image.width;
compressCtx.clearRect(0, 0, compressCanvas.width, compressCanvas.height)
compressCtx.translate(0, 0);
compressCtx.rotate(Math.PI);
compressCtx.drawImage(image, -image.width, -image.height, image.width, image.height);
break;
default:
compressCanvas.height = image.height;
compressCanvas.width = image.width;
compressCtx.clearRect(0, 0, compressCanvas.width, compressCanvas.height)
compressCtx.drawImage(image, 0, 0, image.width, image.height);
}
var base64 = compressCanvas.toDataURL("image/jpeg", .5);

})

}
reader.readAsDataURL(file);
}, false)

示例演示demo

参考资料:

Exif.js 读取图像的元数据
exif-js github
H5拍照应用开发经历的那些坑儿
图片自动旋转的前端实现方案