注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

isblog主页

或许不一定要做得最多,但一定要做得最好! 能力就是一种财富,积累越多,就越体现你

 
 
 

日志

 
 

使用HTML5 File API和Canvas实现图片压缩、旋转、上传  

2015-04-13 09:40:26|  分类: HTML5 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
转自:http://iampeng.wang/javascript/2014/11/10/html5-fileapi-canvas-image.html

       回到今天的主题,最近项目上有一个拍照的需求,对于客户端当然是个小问题,但是产品需要在网页版的页面上同样要实现跟客户端一样的体验!让用户可以在网页上拍照或者选取本地照片上传到服务器。

看到这个需求有点蒙,首先还不确定网页如何调用系统相机,选本地照片的话弄个<input type="file">应该就ok,其次手机拍一张照片都是几兆几兆的,如果不压缩一下图片,在这蛋疼的网络环境下,基本是没办法传到服务器的,网页上的环境也就那样,怎么做图片压缩呢?

网页拍摄照片&选取本地照片

这个看起来很麻烦,其实很简单,网页上可以调用系统原生的照片拍摄和选取操作。直接上代码:

<input type="file" id="photo" accept="image/*" capture="camera">

其中,acceptcaptureHTML5的API,这样在网页上点击这个input之后会弹出拍摄和选取本地照片的窗口,和native app一样。

照片读取、压缩

上面说到,如果不对照片进行处理的话,一张照片就是好几兆,在2g网络下基本不可能成功上传到服务器,所以肯定需要对照片进行压缩了。我们可以使用HTML5的File API配合Canvas绘图实现照片的压缩,代码:

1.读取照片

//绑定input change事件
$("#photo").unbind("change").on("change",function(){
    var file = this.files[0];
    if(file){
        //验证图片文件类型
        if(file.type && !/image/i.test(file.type)){
            return false;
        }
        var reader = new FileReader();
        reader.onload = function(e){
            //readAsDataURL后执行onload,进入图片压缩逻辑
            //e.target.result得到的就是图片文件的base64 string
            render(e.target.result);  
        };
        //以dataurl的形式读取图片文件
        reader.readAsDataURL(file);
    }
});

2.压缩照片

//定义照片的最大高度
var MAX_HEIGHT = 480;
var render = function(src){
    var image = new Image();
    image.onload = function(){
        var cvs = document.getElementById("cvs");
        var w = image.width;
        var h = image.height;
        //计算压缩后的图片长和宽
        if(h>MAX_HEIGHT){
            w *= MAX_HEIGHT/h;
            h = MAX_HEIGHT;
        }
        var ctx = cvs.getContext("2d");
        cvs.width = w;
        cvs.height = h;
        //将图片绘制到Canvas上,从原点0,0绘制到w,h
        ctx.drawImage(image,0,0,w,h);

        //进入图片上传逻辑
        sendImg();
    };
    image.src = src;
};

3.上传照片

var sendImg = function(){
    var cvs = document.getElementById("cvs");
    //调用Canvas的toDataURL接口,得到的是照片文件的base64编码string
    var data = cvs.toDataURL("image/jpeg");
    //base64 string过短显然就不是正常的图片数据了,过滤の。
    if(data.length<48){
        console.log("image data error.");
        return;
    }
    //图片的base64 string格式是data:/image/jpeg;base64,xxxxxxxxxxx
    //是以data:/image/jpeg;base64,开头的,我们在服务端写入图片数据的时候不需要这个头!
    //所以在这里只拿头后面的string
    //当然这一步可以在服务端做,但让闲着蛋疼的客户端帮着做一点吧~~~(稍微减轻一点服务器压力)
    data = data.split(",")[1];
    $.post("./api/uploadimg",{
        fileName:"xxx.jpeg",
        fileData:data
    },function(data){
        if(data.status==200){
            // some code here.
            console.log("commit image success.");
        }else{
            console.log("commit image failed.");
        }
    },"json");
};

看完上面的代码,是不是觉得也没那么难?真的是这样吗?code旅途艰辛,显然没那么容易就让你好过。

测试后发现,在pc上以及大部分android和iphone4s+上是正常的,但是极小部分android和iphone4s以下的机型上得到的照片居然是不完整的!比如只有上半部分,下半部分是黑的,或者照片是旋转的!开始以为是服务端图片存储的时候出了问题,不过后面排除了服务端的问题,看来上面代码是有兼容性问题的。

具体排除问题的过程很复杂纠结,就不细说了。贴几个帖子:

1.HTML5 Canvas drawImage ratio bug iOS

2.iOS HTML5 canvas drawImage vertical scaling bug, even for small images?

3.Drawing on canvas after megapix rendering is reversed

主要是低版本的ios safari上面对于大尺寸的照片(超过设备的物理像素)处理的bug,导致的现象就是上半部分是照片下半部分是黑的,我们需要一个工具将一张大图切成若干个小于屏幕尺寸的小图,分别对小图进行处理然后再合并成一张图片。原理很简单,但实现起来就没那么简单了,还是已经有相关的开源工具来完成这个工作。

Fixes iOS6 Safari's image file rendering issue for large size image (over mega-pixel), which causes unexpected subsampling when drawing it in canvas.

剩下一个图片旋转的问题,其实每张图片拍摄后EXIF里面都带有旋转Orientation字段来标注图片的旋转信息的,也就是说其实图片本身就是倒着的,但是图片展示的时候通过读取Orientation来修正图片展示,使图片能按照拍摄的角度展示,所以我们在写入图片数据的时候需要按照图片本身的Orientation来写入数据,这样我们就需要拿到图片本身的EXIF信息。

JavaScript library for reading EXIF image metadata

ok,问题终于全部排除完毕啦。那么经过优化后的完整代码就是:

//绑定input change事件
$("#photo").unbind("change").on("change",function(){
    var file = this.files[0];
    if(file){
        //验证图片文件类型
        if(file.type && !/image/i.test(file.type)){
            return false;
        }
        var reader = new FileReader();
        reader.onload = function(e){
            //readAsDataURL后执行onload,进入图片压缩逻辑
            //e.target.result得到的就是图片文件的base64 string
            render(file,e.target.result);  
        };
        //以dataurl的形式读取图片文件
        reader.readAsDataURL(file);
    }
});

//定义照片的最大高度
var MAX_HEIGHT = 480;
var render = function(file,src){
    EXIF.getData(file,function(){
        //获取照片本身的Orientation
        var orientation = EXIF.getTag(this, "Orientation");
        var image = new Image();
        image.onload = function(){
            var cvs = document.getElementById("cvs");
            var w = image.width;
            var h = image.height;
            //计算压缩后的图片长和宽
            if(h>MAX_HEIGHT){
                w *= MAX_HEIGHT/h;
                h = MAX_HEIGHT;
            }
            //使用MegaPixImage封装照片数据
            var mpImg = new MegaPixImage(file);
            //按照Orientation来写入图片数据,回调函数是上传图片到服务器
            mpImg.render(cvs, {maxWidth:w,maxHeight:h,orientation:orientation}, sendImg);
        };
        image.src = src;
    });
};

//上传图片到服务器
var sendImg = function(){
    var cvs = document.getElementById("cvs");
    //调用Canvas的toDataURL接口,得到的是照片文件的base64编码string
    var data = cvs.toDataURL("image/jpeg");
    //base64 string过短显然就不是正常的图片数据了,过滤の。
    if(data.length<48){
        console.log("data error.");
        return;
    }
    //图片的base64 string格式是data:/image/jpeg;base64,xxxxxxxxxxx
    //是以data:/image/jpeg;base64,开头的,我们在服务端写入图片数据的时候不需要这个头!
    //所以在这里只拿头后面的string
    //当然这一步可以在服务端做,但让闲着蛋疼的客户端帮着做一点吧~~~(稍微减轻一点服务器压力)
    data = data.split(",")[1];
    $.post("./api/uploadimg",{
        fileName:"xxx.jpeg",
        fileData:data
    },function(data){
        if(data.status==200){
            // some code here.
            console.log("commit image success.");
        }else{
            console.log("commit image failed.");
        }
    },"json");
};

ok,结果一番折腾后,终于在所有设备上都能正常运行了:)



  评论这张
 
阅读(525)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018