图片上传自动旋转、压缩

转载自林鑫的博客

前言

最近工作中遇到一个在前端上传图片后需求自动调整图片方向的需求,查找了一下资料,结合自己的工作总结如下。

背景

通过网页 input 标签上传图片,有一些使用手机拍摄的图片会出现旋转了90度的问题,包括 iPhone 和个别三星手机。这些手机竖着拍的时候才会出现这种问题,横拍出来的照片就正常显示。因此,可以通过获取手机拍照方向来对照片进行旋转,从而解决这个问题。

Orientation

orientation,英文翻译为方向,这个参数不是所有图片都有,但手机拍摄的照片是有的,所以我们需要在处理时判断一下。

旋转角度 参数值
1
顺时针90° 6
逆时针90° 8
180° 3

参数为 1 的时候显示正常,即手机横拍显示正常,Orientation = 1 ,而手机竖拍的参数为 6。

想要获取 Orientation 参数,可以通过 exif.js 库来操作。exif.js 功能很多,体积也很大,未压缩之前足足有 30k,这对手机页面加载来说是非常大影响的。而我只需要获取 Orientation 信息而已,所以这里使用原文作者精简后exif.js 库,将代码缩小到6KB。

exif.js 获取 Orientation :

EXIF.getData(file, function() {  
    var Orientation = EXIF.getTag(this, 'Orientation');
});

其中file是通过input标签获取的图片,比如var file = $('#input').props.files[0],而其中exif.js库的原理是把file通过FileReader对象读取出来,然后使用DataView对象转换成二进制数组的格式,然后不断读取二进制数据的字节信息,判断出图片的方向,返回一个封装好的orientation值。

旋转

获取到方向后,我们就要把图片旋转了,可能你会想到直接操作CSS的transfer rotate方法,但对于加了预加载功能的图片,这样会在界面上看到一个旋转的动画,实在不优雅。所以我们使用canvas来操作,还能加入压缩的功能,使图片加载更快。

ctx.rotate(angle),rotate 方法的参数为旋转弧度。需要将角度转为弧度:degrees * Math.PI / 180,旋转的中心点默认都在 canvas 的起点,即 ( 0, 0 )。旋转的原理如下图:

旋转之后,如果从 ( 0, 0 ) 点进行 drawImage(),那么画出来的位置就是在左图中的旋转 90 度后的位置,这时视图不在可视区域了,而旋转之后,坐标轴也跟着旋转了,为了显示在可视区域,需要将图片点往 y 轴的反方向移 y 个单位,此时的起始点则为 ( 0, -y )。

同理,可以获得旋转 -90 度后的起始点为 ( -x, 0 ),旋转 180 度后的起始点为 ( -x, -y )。

压缩

手机拍出来的照片太大,而且使用 base64 编码的照片会比原照片大,那么在进行预览的时候进行压缩是有必要的。现在的手机像素这么高,拍出来的照片宽高都有几千像素,用 canvas 来渲染这照片的速度会相对比较慢。

因此第一步需要先对上传照片的宽高做限制,判断宽度或高度是否超出哪个范围,则等比压缩其宽高。

var ratio = width / height;
if(imgWidth > imgHeight && imgWidth > xx){
    imgWidth = xx;
    imgHeight = Math.ceil(xx / ratio);
}else if(imgWidth < imgHeight && imgHeight > yy){
    imgWidth = Math.ceil(yy * ratio);
    imgHeight = yy;
}

第二步就通过 canvas.toDataURL() 方法来压缩照片质量。

canvas.toDataURL("image/jpeg", 1);

toDataURL() 方法返回一个包含图片展示的 data URI 。使用两个参数,第一个参数为图片格式,默认为 image/png。第二个参数为压缩质量,在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。

核心代码

<input type="file" id="files" >
<img src="blank.gif" id="preview">
<script src="small-exif.js"></script>
<script>
var ipt = document.getElementById('files'),
    img = document.getElementById('preview');

ipt.onchange = function (e) {
    var file = e.target.files[0];

    if(file){
        EXIF.getData(file, function() {
            // 先获取方向
            var Orientation = EXIF.getTag(this, 'Orientation');

            // 因为是在回调函数里设置orientation的值,所以要在回调成功后再读取图片,保持同步
            var reader = new FileReader(),
                image = new Image();

            reader.onload = function (ev) {// 读取文件
                image.onload = function () {// 加载照片
                    var imgWidth = this.width,
                        imgHeight = this.height;
                    // 控制上传图片的宽高
                    if(imgWidth > imgHeight && imgWidth > 750){
                        imgWidth = 750;
                        imgHeight = Math.ceil(750 * this.height / this.width);
                    }else if(imgWidth < imgHeight && imgHeight > 1334){
                        imgWidth = Math.ceil(1334 * this.width / this.height);
                        imgHeight = 1334;
                    }

                    var canvas = document.createElement("canvas"),
                        ctx = canvas.getContext('2d');
                    canvas.width = imgWidth;
                    canvas.height = imgHeight;

                    if(Orientation && Orientation != 1){
                        switch(Orientation){
                            case 6:     // 旋转90度
                                canvas.width = imgHeight;
                                canvas.height = imgWidth;
                                ctx.rotate(Math.PI / 2);
                                ctx.drawImage(this, 0, -imgHeight, imgWidth, imgHeight);
                                break;
                            case 3:     // 旋转180度
                                ctx.rotate(Math.PI);
                                ctx.drawImage(this, -imgWidth, -imgHeight, imgWidth, imgHeight);
                                break;
                            case 8:     // 旋转-90度
                                canvas.width = imgHeight;
                                canvas.height = imgWidth;
                                ctx.rotate(3 * Math.PI / 2);
                                ctx.drawImage(this, -imgWidth, 0, imgWidth, imgHeight);
                                break;
                        }
                    }else{
                        ctx.drawImage(this, 0, 0, imgWidth, imgHeight);
                    }
                    img.src = canvas.toDataURL("image/jpeg", 0.8);
                };
                image.src = ev.target.result;// 这里一定要保证代码的顺序,必须先创建image.onload,否则可能图片一下就加载完了
            };
            reader.readAsDataURL(file);// 这里一定要保证代码的顺序,必须先创建reader.onload,否则可能文件一下就读取完了
        });
    }
}
</script>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容