可参考下图理解
资料: http://www.cocoachina.com/ios/20150605/12021.html
https://www.jianshu.com/p/ad4501db178e/
解决思路:
引入了一个叫做 exif.js 的库来实现旋转角度的纠正,它提供了js读取图像的原始数据的功能扩展,例如:拍照方向、相机设备型号、拍摄时间、ISO 感光度、GPS 地理位置等数据。
解决方法:
1.在保存图片至服务器之前读取图片的拍照方向信息,根据旋转角度做处理,将处理后的图片上传至服务器,显示
优点:预览的图片和大后台审核的图片一致
缺点:据开发说比较难处理
2.将图片上传至服务器,前端读取图片信息,在展示前做旋转角度处理,显示
优点:能快速解决当前出现的问题
缺点:大后台审核的小伙伴审核时看图比较难受
Andriod的情况就比较复杂了,就我们公司现有的机型来看,得出以下结论,垂直角度拍摄上传,显示正常
唯一一个 有问题的是三星手机参考资料 https://www.jianshu.com/p/01d0fd4b4bfe ,跟ios问题一样
资料: https://www.jianshu.com/p/7d88ec1347b6
HTML:
<!--添加-->
<div class="dianji" @click="triggerUpload">添加图片</div>
<input type="file" id="upload" accept="image/*capture=camera" value="" @change="upload">
JS:
triggerUpload() { //触发input的点击事件,用户选择图片进行上传
document.getElementById("upload").click()
},
//上传图片
upload(e) {
let self = this
let file = e.target.files[0] || e.dataTransfer.files[0]
let Orientation
//去获取拍照时的信息,解决拍出来的照片旋转问题
Exif.getData(file, function() {
Orientation = Exif.getTag(this, 'Orientation')
})
// 看支持不支持FileReader
if(!file || !window.FileReader) return
if(/^image/.test(file.type)) {
// 创建一个reader
let reader = new FileReader()
// 将图片2将转成 base64 格式
reader.readAsDataURL(file)
// 读取成功后的回调
reader.onloadend = function() {
let result = this.result
let img = new Image()
img.src = result
e.target.value=''//input file 重复上传同一张图片失效的解决办法
let uploadImagesItem = {
msrc: this.result,
src: this.result,
Attachmentid: 0,
Newfilename: this.result
}
//现在是判断图片是否大于250k,是就直接上传,反之压缩图片
if(this.result.length <= (250 * 1024)) {
self.uploadImages.push(uploadImagesItem)
self.postImg(this.result)//提交图片到后台
} else {
img.onload = function() {
uploadImagesItem = {
msrc: self.compress(img, Orientation),
src: self.compress(img, Orientation),
Attachmentid: 0,
Newfilename: self.compress(img, Orientation)
}
self.uploadImages.push(uploadImagesItem)
let data = self.compress(img, Orientation)
self.postImg(data)
}
}
}
}
},
postImg() {
//这里写接口
},
//旋转照片, 利用exif.js解决ios手机上传竖拍照片旋转90度问题
rotateImg(img, direction, canvas) {
//最小与最大旋转方向,图片旋转4次后回到原方向
const min_step = 0
const max_step = 3
if(img == null) return
//img的高度和宽度不能在img元素隐藏后获取,否则会出错
let height = img.height
let width = img.width
let step = 2
if(step == null) {
step = min_step
}
if(direction == 'right') {
step++
//旋转到原位置,即超过最大值
step >max_step &&(step = min_step)
} else {
step--
step <min_step &&(step = max_step)
}
//旋转角度以弧度值为参数
let degree = step * 90 * Math.PI / 180
let ctx = canvas.getContext('2d')
switch(step) {
case 0:
canvas.width = width
canvas.height = height
ctx.drawImage(img, 0, 0)
break
case 1:
canvas.width = height
canvas.height = width
ctx.rotate(degree)
ctx.drawImage(img, 0, -height)
break
case 2:
canvas.width = width
canvas.height = height
ctx.rotate(degree)
ctx.drawImage(img, -width, -height)
break
case 3:
canvas.width = height
canvas.height = width
ctx.rotate(degree)
ctx.drawImage(img, -width, 0)
break
}
},
//压缩图片
compress(img, Orientation) {
let canvas = document.createElement("canvas")
let ctx = canvas.getContext('2d')
//瓦片canvas
let tCanvas = document.createElement("canvas")
let tctx = tCanvas.getContext("2d")
let initSize = img.src.length
let width = img.width
let height = img.height
//如果图片大于四百万像素,计算压缩比并将大小压至400万以下
let ratio
if((ratio = width * height / 4000000) >1) {
console.log("大于400万像素")
ratio = Math.sqrt(ratio)
width /= ratio
height /= ratio
} else {
ratio = 1
}
canvas.width = width
canvas.height = height
// 铺底色
ctx.fillStyle = "#fff"
ctx.fillRect(0, 0, canvas.width, canvas.height)
//如果图片像素大于100万则使用瓦片绘制
let count
if((count = width * height / 1000000) >1) {
console.log("超过100W像素")
count = ~~(Math.sqrt(count) + 1)//计算要分成多少块瓦片
// 计算每块瓦片的宽和高
let nw = ~~(width / count)
let nh = ~~(height / count)
tCanvas.width = nw
tCanvas.height = nh
for(let i = 0i <counti++) {
for(let j = 0j <countj++) {
tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh)
ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh)
}
}
} else {
ctx.drawImage(img, 0, 0, width, height)
}
//修复ios上传图片的时候 被旋转的问题
if(Orientation != "" &&Orientation != 1) {
switch(Orientation) {
case 6: //需要顺时针(向左)90度旋转
this.rotateImg(img, 'left', canvas)
break
case 8: //需要逆时针(向右)90度旋转
this.rotateImg(img, 'right', canvas)
break
case 3: //需要180度旋转
this.rotateImg(img, 'right', canvas)//转两次
this.rotateImg(img, 'right', canvas)
break
}
}
//进行最小压缩
let ndata = canvas.toDataURL('image/jpeg', 0.1)
console.log('压缩前:' + initSize)
console.log('压缩后:' + ndata.length)
console.log('压缩率:' + ~~(100 * (initSize - ndata.length) / initSize) + "%")
tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0
return ndata
},
它的主要功能就是上传两张人像,通过算法进行分析对比,最后得出一个相似度的分数,以验证你们是天造地设还是颜值互补。但是,当我们把上传的图片转换成base64格式,发送给后台时,会发现偶尔会出现问题,有一些图片本来是这样的:
柴犬
处理之后却变成了这样:
柴犬2
经过测试发现,只有iOS手机竖着拍的照片才会出现这样的问题,而iOS手机横着拍的照片、Android手机拍的照片以及通过屏幕截图、网络下载等途径获得的图片都不会产生这个问题。
那么,这到底是为什么呢?
在开发过程中,由于时间紧迫,未求甚解,使用了github上的一个开源项目 lrz.js 来解决此问题,这个工具的主要用途是在尽量保证图片质量的前提下压缩图片的大小,但同时也附带了图片旋转角度纠正的功能。
通过阅读 lrz.js 的源代码,我发现它引入了一个叫做 exif.js 的库来实现旋转角度的纠正,它提供了js读取图像的原始数据的功能扩展,例如:拍照方向、相机设备型号、拍摄时间、ISO 感光度、GPS 地理位置等数据。而拍照方向就是关键所在!
exif.js 获取图像的拍照方向的代码如下:
EXIF.getData(IMG_FILE, function () { // IMG_FILE为图像数据
var orientation = EXIF.getTag(this, "Orientation")
console.log("Orientation:" + orientation)// 拍照方向
})
获取拍照方向的结果为1-8的数字:
拍照方向信息
注意:对于上面的八种方向中,加了*的并不常见,因为它们代表的是镜像方向,如果不做任何的处理,不管相机以任何角度拍摄,都无法出现镜像的情况。
这个表格代表什么意义?我们来看第一行,值为1时,右边两列的值分别为:Row #0 is Top,Column #0 is Left side,其实很好理解,它表示照片的第一行位于顶端,而第一列位于左侧,那么这张照片自然就是以正常角度拍摄的。
而这8种结果,就是第一行与第一列所在的位置的8种组合。
那么,我们来测试一下iOS手机横着拍的照片,来看看它的拍照方向是什么呢?
测试1
结果是1,即以正常角度拍摄的,其实也就是原图啦~
那么,我们再测试一下iOS手机竖着拍的照片,来看看它的拍照方向是什么呢?
测试2
原来是6!即第一行位于右侧,第一列位于顶端,其实相当于将照片顺时针旋转了90度!
所以,实际上iOS手机竖着拍出的照片与横着拍出的照片其本质上是一样的,只不过竖着拍出的照片被添加了一个顺时针旋转90°的拍照方向,所以显示的时候,就变成了上下边窄左右边宽的状态,其实也就是横着拍的照片顺时针旋转90°而成的~
那么明白了这些,文章开头所说的照片旋转bug的原因,也就很简单啦~
其实就是当我们在前端对图片进行像素处理或者drawInRect等操作之后,照片的Orientaion信息,即为拍照方向信息被删除了,所以iOS手机竖着拍的照片又回到了横着的状态,看起来也就是逆时针旋转了90°!
那么如何纠正这个旋转角度呢?
其实思路也很简单:在处理图片之前,先读取并保存图片的拍照方向信息,然后在处理图片之后,再根据拍照方向,对图片进行相应的调整,lrz.js 中的代码如下:
switch (orientation) {
case 3:
ctx.rotate(180 * Math.PI / 180)
ctx.drawImage(img, -resize.width, -resize.height, resize.width, resize.height)
break
case 6:
ctx.rotate(90 * Math.PI / 180)
ctx.drawImage(img, 0, -resize.width, resize.height, resize.width)
break
case 8:
ctx.rotate(270 * Math.PI / 180)
ctx.drawImage(img, -resize.height, 0, resize.height, resize.width)
break
case 2:
ctx.translate(resize.width, 0)
ctx.scale(-1, 1)
ctx.drawImage(img, 0, 0, resize.width, resize.height)
break
case 4:
ctx.translate(resize.width, 0)
ctx.scale(-1, 1)
ctx.rotate(180 * Math.PI / 180)
ctx.drawImage(img, -resize.width, -resize.height, resize.width, resize.height)
break
case 5:
ctx.translate(resize.width, 0)
ctx.scale(-1, 1)
ctx.rotate(90 * Math.PI / 180)
ctx.drawImage(img, 0, -resize.width, resize.height, resize.width)
break
case 7:
ctx.translate(resize.width, 0)
ctx.scale(-1, 1)
ctx.rotate(270 * Math.PI / 180)
ctx.drawImage(img, -resize.height, 0, resize.height, resize.width)
break
default:
ctx.drawImage(img, 0, 0, resize.width,resize.height)
}
其中,translate是平移变换,scale(-1,1)是向左翻转,rotate是顺时针旋转。
举例说明 case 2,当图片的拍照方向为2时,即第一行位于顶端,而第一列位于右侧,其实相当于把照片进行了左右的翻转。所以,这里对图片的操作是,先向右平移等于图片宽度的距离,再向左翻转,这相当于以图片水平方向的对称轴为轴进行了左右翻转,然后再以(0,0)为起始点绘制原宽高的图片,即完成了对拍照方向的纠正。
最后
经过一系列的测试,发现确实只有iOS手机的竖拍照片与横拍照片是通过拍照方向来区别的,Android手机无论竖拍还是横拍的照片,拍照方向都为1,也就是说即使丢失了拍照方向这一信息,也不会影响到图片的旋转角度。而手机或电脑的屏幕截图、网络上的图片、通过PS制作的图片等也是如此。
作者:任无名F
链接:http://www.jianshu.com/p/ad4501db178e
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。