关于“H5端IOS中拍照上传后图片被旋转”问题的探索和解决

2,953 阅读6分钟

前言

在有关图片上传的开发中我们有时候会遇到看着正常的图片,上传以后会莫名的旋转了。
比如移动端开发中在IOS 较低版本中就会出现这种情况,当我们拍照的时候,把图片渲染到canvas的时候,canvas渲染出来的图片就被旋转了。
那么这到底是怎么回事呢。这就涉及到了一种叫Exif的东西。

什么是Exif

Exif(Exchangeable image file format)也就是可交换图像文件格式。是专门用来记录数码照片属性信息和拍摄数据的。Exif信息包含了非常多的元数据,比如:
1、图像相关的有:图像ID、分辨率、分辨率单位、图像方向、图像宽高等
2、相机相关的有:相机制造商、相机型号、光圈值、焦距、物距、光圈值等
3、其他一些: 饱和度、亮度、清晰度、EXIF版本等
那么这些中对我们遇到的图片旋转起到作用的明显就是图片方向。

如何解决

我们发现图片方向有几个值分别是:

orientation值旋转角度
10
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
微信图片_20210507161503.jpg
B、ios 12.5版本显示(用手机进行不同方向的拍摄会得到不同的orientation情况)
image.png
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
image.png
B、ios 12.5版本
IMG_E0002.JPG
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信息的获取方法。