在上一节中已经提到了预览,预览可以通过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”都不行

 

 

参考资料:

H5拍照应用开发经历的那些坑儿

图片自动旋转的前端实现方案

JPEG Rotation and EXIF Orientation

利用exif.js解决ios手机上传竖拍照片旋转90度问题

Html5 create thumbnail

 

 posted on 2016-02-23 10:00  咖啡机(K.F.J)  阅读(2756)  评论(1编辑  收藏  举报