前言
在有关图片上传的开发中我们有时候会遇到看着正常的图片,上传以后会莫名的旋转了。
比如移动端开发中在IOS 较低版本中就会出现这种情况,当我们拍照的时候,把图片渲染到canvas的时候,canvas渲染出来的图片就被旋转了。
那么这到底是怎么回事呢。这就涉及到了一种叫Exif的东西。
什么是Exif
Exif(Exchangeable image file format)也就是可交换图像文件格式。是专门用来记录数码照片属性信息和拍摄数据的。Exif信息包含了非常多的元数据,比如:
1、图像相关的有:图像ID、分辨率、分辨率单位、图像方向、图像宽高等
2、相机相关的有:相机制造商、相机型号、光圈值、焦距、物距、光圈值等
3、其他一些: 饱和度、亮度、清晰度、EXIF版本等
那么这些中对我们遇到的图片旋转起到作用的明显就是图片方向。
如何解决
我们发现图片方向有几个值分别是:
orientation值 | 旋转角度 |
---|---|
1 | 0 |
2 | 左右翻转 |
3 | 顺时针180度 |
4 | 上下翻转 |
5 | 顺时针翻转90度后,左右翻转 |
6 | 顺时针90度 |
7 | 逆时针翻转90度后,左右翻转 |
8 | 逆时针90度 |
造成这种现象的问题就是可能会出现3、6、8的情况,其他几种左右翻转情况不知道何时会出现(有知道的朋友们欢迎评论区留言)。那么我们要怎么处理呢。
我们直观的反应有两个:
1、既然这个值有问题那么我们就直接改掉它。
2、既然被旋转了,那么根据图片的orientation值,对图片旋转方向进行修正。
下面我们来分别调研一下这两种方案吧。
方案调研
方案一
直接修改orientation这个值,这里我们可以使用piexifjs,它既可以获取exif信息也可以修改exif信息。最后会生成一个base64的图片。我们这里做了四个元素分别进行比较:
1、直接显示原图
2、原图渲染到canvas上显示
3、直接显示修改exif信息的图片
4、修改exif信息后的图片在canvas上渲染
下面我跟分别对(1、2)(1、3)(3、4)(2、4)进行比较。代码如下:
<html>
<head>
<title>直接修改exif信息</title>
</head>
<body>
<input type="file" accept="image/*" onchange="upload(this)" />
<div>
<img src="" id="origin" width="200"/>
<p>原图</p>
<canvas id="originImage" ></canvas>
<p>canvas渲染的原图</p>
</div>
<div>
<img src="" id="image" width="200"/>
<p>修改exif信息后的图片</p>
<canvas id="resultImage" style="width: 200px;"></canvas>
<p>canvas渲染的处理exif信息的图片</p>
</div>
<script src="./piexif.js"></script>
<script>
var imageDom = document.getElementById('image')
var originDom = document.getElementById('origin')
function getOri(exif) {
return exif["0th"][piexif.ImageIFD.Orientation]
}
function initCanvas(image, id) {
var { width, height } = image;
var canvas = document.getElementById(id)
var ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
ctx.drawImage(image, 0, 0, width, height);
return canvas;
}
function upload(target) {
var file = target.files[0];
var reader = new FileReader();
reader.onload = function (e) {
var result = e.target.result
var originExif = piexif.load(result) // 获取exif信息
const orientation = getOri(originExif) // orientation
imageDom.onload = function() {
initCanvas(imageDom, 'resultImage')
}
if(orientation && orientation != 1) {
let exif = {};
exif[piexif.ImageIFD.Orientation] = 2; // 设置orientation
var exifObj = {"0th":exif};
var exifbytes = piexif.dump(exifObj);
var inserted = piexif.insert(exifbytes, result); // 写入新的exif
imageDom.src = inserted;
var resultExif = piexif.load(inserted);
console.log(resultExif, 'result')
console.log(originExif, 'origin')
} else {
imageDom.src = result;
}
originDom.onload = function(){
var canvas = initCanvas(originDom, 'originImage');
var url = canvas.toDataURL("image/jpeg", 0.8);;
var canvasResultExif = piexif.load(url)
console.log(canvasResultExif, 'canvasResult')
}
originDom.src = result
};
reader.readAsDataURL(file);
}
</script>
</body>
</html>
通过进行各种图片以及修改各种exif信息的后。我们这里展示orientation为6的图片,并且exif中orientation设置为2,这样我们的对比效果更加明显:
A、Ios 14.0.1
B、ios 12.5版本显示(用手机进行不同方向的拍摄会得到不同的orientation情况)
A结果给我们的现象是:
1、原图正常显示
2、原图渲染到canvas也正常显示
3、修改exif信息的图片按照的是原图的orientation为1的情况下进行了翻转,也就是orientation变成了2(当然我们这里设置的就是2,这个结果符合我们预期)
4、exif修改后渲染到canvas上也跟直接修改exif信息的图片一样
B结果给我们的现象是:
1、原图正常显示
2、原图渲染到canvas上的表现为orientation为1
3、修改exif信息的图片的显示情况为orientation为2(因为我们把它的值修改为了2)
4、exif修改后渲染到canvas上的表现结果为orientation为1
对这些exif信息进行打印,发现canvas渲染的图片的exif信息进行了丢失。
通过两组结果的1、2、3、4对比,我们不难得出在较低版本的IOS中canvas的渲染情况按照图片的orientation为1进行渲染。
而在较高版本中canvas的渲染情况为图片的实际exif设置值。
可见第一种方案并不可行。
方案二
根据图片的orientation值,对图片旋转方向进行修正。我们可以使用piexifjs或者exifjs来读取orientation值,然后进行处理。
虽然piexifjs和exifjs获取orientation的api不太一样,不过我们后续处理图片的方式就跟这两者无关了。但是因为上面我们用了piexifj,这里我们仍然继续使用它吧。
我们同样按照方案一进行分组对比。
<html>
<head>
<title>方案二,旋转</title>
</head>
<body>
<input type="file" accept="image/*" onchange="upload(this)" />
<div>
<img src="" id="origin" width="200" />
<canvas id="originImage" style="width: 200px;"></canvas>
<p>原图</p>
</div>
<div>
<img src="" id="image" width="200" />
<canvas id="resultImage" style="width: 200px;"></canvas>
<p>结果</p>
</div>
<script src="./piexif.js"></script>
<script>
var imageDom = document.getElementById('image')
var originDom = document.getElementById('origin')
function getOri(image) {
var exif = piexif.load(image);
return exif["0th"][piexif.ImageIFD.Orientation]
}
function initCanvas(image, id) {
var { width, height } = image;
var canvas = null
if(id) {
canvas = document.getElementById(id)
} else {
canvas = document.createElement('canvas')
}
if(!canvas) return ;
var ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
ctx.drawImage(image, 0, 0, width, height);
return {
canvas,
ctx
}
}
function drawImageInPage(src, id) {
var image = new Image;
image.onload = function(){
initCanvas(image, id)
}
image.src = src;
}
function upload(target) {
var file = target.files[0];
var reader = new FileReader();
reader.onload = function (e) {
var result = e.target.result
var orientation = getOri(result);
var image = new Image;
image.onload = function () {
var { canvas, ctx } = initCanvas(image)
rotateImage(orientation, canvas, ctx, image)
const dataurl = canvas.toDataURL("image/jpeg", 0.8);
imageDom.src = dataurl
drawImageInPage(dataurl, 'resultImage')
}
image.src = result;
originDom.src = result
drawImageInPage(result, 'originImage')
};
reader.readAsDataURL(file);
}
const Ori = Object.freeze({
zero: 1,
clockwise180: 3,
clockwise90: 6,
antiClockwise90: 8
})
// 对图片进行旋转操作
function rotateImage(orientation, canvas, ctx, image) {
if (orientation && orientation != Ori.zero) {
switch (orientation) {
case Ori.clockwise90:
canvas.width = image.height;
canvas.height = image.width;
ctx.rotate(Math.PI / 2);
ctx.drawImage(image, 0, -image.height, image.width, image.height);
break;
case Ori.clockwise180:
ctx.rotate(Math.PI);
ctx.drawImage(image, -image.width, -image.height, image.width, image.height);
break;
case Ori.antiClockwise90:
canvas.width = image.height;
canvas.height = image.width;
ctx.rotate((3 * Math.PI) / 2);
ctx.drawImage(image, -image.width, 0, image.width, image.height);
break;
}
ctx.drawImage(image, 0, 0);
}
}
</script>
</body>
</html>
显示结果:
A、Ios 14.0.1
B、ios 12.5版本
A结果给我们的现象是:
1、原图正常显示
2、原图渲染到canvas也正常显示
3、由于orientation为6,所以图片按照逻辑进行了旋转,也就是在orientation为6的角度上进行了旋转90度
4、生成的同3的方向一样
B结果给我们的现象是:
1、原图正常显示
2、原图渲染到canvas上的表现为orientation为1
3、显示同原图,也就是在orientation为1的情况下旋转了90度
4、显示同3一样,因为此时exif信息已经丢失,所以再按照3进行渲染结果肯定跟3一样
通过两组结果的1、2、3、4对比,再一次验证了方案一的结果。但是出现了新的问题,我们发现在较高版本的ios中进行了旋转反而显示情况是错误的。
此时我们可以通过设置一个隐藏图片,来判断图片是否进行了旋转,也就是2参照组是否跟1一样来判断canvas是否如预期绘制图片。从而决定是否可以在此环境中对显示图进行旋转。
总结
1、在较低版本的IOS中canvas的渲染情况按照图片的orientation为1进行渲染。而在较高版本中canvas的渲染情况为图片的实际exif值。
2、canvas渲染完图片以后,图片exif信息丢失。
3、在高版本的时候可以通过判断预设的图片是否进行了旋转,来决定是否要对显示图片进行旋转。
这次我们初步的了解了如何通过获取exif信息对IOS拍照问题进行处理。下次有时间再学一下exifjs的实现原理,来探究一下exif信息的获取方法。