在上一节中已经提到了预览,预览可以通过data: URL格式或URL对象。
var file = upload.files[0]; //URL对象 var url = URL.createObjectURL(file); var img = new Image(); img.style.width = '100%'; img.src = url; img.onload = function(e) { window.URL.revokeObjectURL(this.src); //销毁 }
//data:URL格式 var img = new Image(); img.style.width = '100%'; var reader = new FileReader(); reader.onload = function() { img.src = this.result; }; reader.readAsDataURL(file);
一切都很顺利,但其实有很多的坑,需要慢慢讲来。先从前面做的一个小功能说起。
这个小功能就是将两张图片合成起来,组成一张新的图片。
一、技术实现
1)上传的控件就用“input[type=file]”实现
Android的webview不支持此控件,怪不得按钮怎么按都按不动,后面只得用客户端提供的接口做上传
期间发现UC浏览器会自动将上传控件包装成三个按钮的样子,只需设置为“opacity:0;”即可。
2)使用“FileReader”,通过方法readAsDataURL获取data URL格式的图片内容,赋值给image。
IOS在上传图片或拍照的时候,显示出来的宽高是反的,这个时候我们增加了一步旋转的操作,让用户自己来控制。
在给image赋src值的同时,赋“crossOrigin”的值,用于后面的图片跨域
3)将图片画在“canvas”上,旋转其实也就是转这个canvas。通过计算坐标和角度实现旋转
4)将旋转后的“canvas”与剩下的两张文字图片,通过一个新的“canvas”合并到一起。
在将两个“canvas”画在一起后,发现通过webview上传的图片,不能使用“toDataURL()”方法了,这是因为画布被污染,第一个画布中的图片是跨域的。
5)最后将data URL格式的内容上传到服务器中保存。
总共开发时间是3天,在这个过程中有过几次技术推翻重做或者逻辑重整,兼容性方面也还有很大问题,包括图片的宽高的自适应等,还很不完善。
下图是最终效果,挑了张松狮的写真照:
二、预览
原先我在做的时候,第2步和第3布是合在一起的,也就是图片显示出来的时候,已经校准好位置。
只是在实现的过程中碰到了很多麻烦,并且时间仓促,BUG修复起来比较费时,只得分两步。
先说说原先的过程:
1)IOS图片上传或拍照,宽高相反
我用的是Android手机,刚开始并没有发现这个问题,后面别人帮忙测试的时候,才发现了这个重大问题,接下来就是折腾这个事儿,想要自动校正方向。
1. 通过exif.js获取图片元数据,通过获取“Orientation”属性判断方向【我的Android机中这个属性为undefined,IOS有值】,总共有8个值
左边是说明,右边是展示。表格中带“*”的是指翻转过来了。
接下来就是做计算,网上有很多计算方式,对于“>IOS7”的系统,计算逻辑可以参考下localResizeIMG插件195行。
顺便说下,这个插件使用到了gulp的开发方式,如果要调试就要配置相关代码,可以参考《前端自动化构建工具gulp记录》
插件中还大量使用了Promises/A+规范,关于这个规范可以参考《JavaScript中Promises/A+规范的实现》
exif.getData(typeof file === 'object' ? file : img, function() { orientation = exif.getTag(this, "Orientation"); //计算逻辑.... });
2. “<=IOS7”系统的计算方式略有不同。IOS6中图片拍照上传会被压扁(当照片超过2M时),这是IOS6的BUG,较大的图片可能会发生。
计算逻辑也可以参考localResizeIMG插件165行。要解决这个BUG需要引入megapix-image.js。
require(['megapix-image'], function(MegaPixImage) { var mpImg = new MegaPixImage(img); //计算逻辑 });
2)将画布“canvas”通过方法“toDataURL”,变成data URL格式
1. 在手机QQ浏览器中,canvas对象使用toDataURL方法获取不到任何数据,需要引用jpeg-encoder.js解决。
var cvs = document.createElement('canvas'); var ctx = cvs.getContext("2d"); ctx.drawImage(theImg, 0, 0); var theImgData = ctx.getImageData(0, 0, cvs.width, cvs.height); // Encode the image and get a URI back, toRaw is false by default var jpegURI = encoder.encode(theImgData, quality);
在开发的时候方向自动旋转花了很多时间。碰到各种问题,例如“orientation”属性获取不到、自动旋转的角度不对。
后面贪简单,就找了个canvasResize插件,虽然会自动校正,但是图片有点模糊,而且图片的宽高不容易控制。效果不尽如意,只好分两步做。
最终的过程:
1)添加旋转页面
先计算起始坐标点X与Y,简单点就是宽度和高度各除以2,再通过canvas的“translate”,再用“rotate”计算角度。在“drawImage”的时候X和Y点变成负数,移位过去。
var xpos = canvas_width / 2; var ypos = canvas_height / 2; ctx.translate(xpos, ypos); ctx.rotate(angle * Math.PI / 180); ctx.drawImage(rotate_img, -width / 2, -height / 2, width, height);
三、合成
在上图中点击确认就会自动合成。
1)图片宽高自适应
画布的宽高只做了简单的比率,画布的宽度除以图片的宽度,我把头部的图片和底部的图片宽度都做的相同,下图所示:
var multiple = width / header.width;//画布宽度除以图片宽度 var header_height = header.height * multiple;//顶部图片 var footer_height = footer.height * multiple;//底部图片 var footer_y = canvas_height - footer_height;
2)将顶部与底部图片、与上一个旋转后的canvas画一起在第二个画布上
ctx.drawImage(rotate_canvas, 0, 0, width, height); ctx.drawImage(header, 0, 25, width, header_height); ctx.drawImage(footer, 0, footer_y, width, footer_height);
这里碰到了一个画布污染的问题。上面的“rotate_canvas”,图片的获取有两种,一个是通过“file”控件上传的,另一个是在webview提供的接口上传的。
两个唯一的不同是跨域,第二个接口返回的是另外一个域名中的图片。图片跨域了,那么rotate_canvas也算是跨域了。
跨域的话,会影响“toDataURL()”方法。这个时候就需要img图片跨域。
1. 图片设置crossOrigin属性,这是一个HTML5属性,兼容性不是很好,测试了几台Android机,有的行,有的不行。“crossOrigin”属性设置要在“src”之前,否则IOS不可使用。
rotate_img.crossOrigin = "Anonymous";
2. 服务器需要设置,可以正确响应 Access-Control-Allow-Origin 头。
调试的时候,如果服务器还没配置,可以用Fiddler模拟响应头。
a. 先找到filter选项
b. 设置请求头
c. 设置响应头,注意与请求头中的内容要一模一样,少个“http”都不行
参考资料:
JPEG Rotation and EXIF Orientation