您的位置:首页 > 移动开发

移动端Web上传图片实践

2015-07-18 11:41 495 查看
原文链接

其他链接: http://tgideas.qq.com/webplat/info/news_version3/804/808/811/m579/201409/278736.shtml
从iOS 6+、Android 3+开始 (来源http://mobilehtml5.org/),移动端可以通过网页中的
<input
type="file">
来拍照上传或是上传相册中的照片。 不过从图片上传到服务器后可能会遇到图片莫名其妙旋转的问题,如图





一些设备在拍照时明明是竖着拍的(右),传到服务端后从图片查看器中看到的是横着的(左),而在一些图片处理工具或是浏览器中通过http协议看到的却是正常的,原因是在照片的exif中的Orientation属性控制了照片的旋转方向。
//Exif 信息示例
Exif.Image.Make                              Ascii       6  Canon
Exif.Image.Model                             Ascii      20  Canon PowerShot S40
Exif.Image.Orientation                       Short       1  top, left
Exif.Image.XResolution                       Rational    1  180
Exif.Image.YResolution                       Rational    1  180
Exif.Image.ResolutionUnit                    Short       1  inch
Exif.Image.DateTime                          Ascii      20  2003:12:14 12:01:44
Exif.Image.YCbCrPositioning                  Short       1  Centered
......


大部分图片查看器和编辑工具都会去根据这个属性控制照片方向,而windows自带的查看器等工具并不理睬他。

为什么要有Orientation属性呢,我并没有找到官方的解释,见到种说法挺有道理:几乎所有的摄像头在出场的时候成相芯片都是有方向的,拍出来的照片的像素都是默认方向的。如果每拍一张照片就对这些像素进行旋转,如果数码相机每秒连拍20张来算,旋转操作将会非常耗时。更聪明的做法是拍照时只记录一个方向,然后显示的时候按方向显示出来即可。因此exif定义了一个标准的方向参数,只要读图的软件都来遵守规则,加载时候读取图片方向,然后做相应的旋转即可。这样既可以达到快速成像的目的,又能达到正确的显示。





修正图片方向的问题

为了图片显示不出问题,我们得修改Orientation属性。首先想到的是服务端来修正方案,对于图片来说exif信息不是必须的,可以根据Orientation的值来对照片进行手动矫正的操作,然后再去掉exif,类似这样:
switch(exif['Orientation']){
case 2:
image->save;
break;
case 3:
image->rotate(-180)->save;
break;
case 4:
image->rotate(180)->save;
break;
······
}


更好的方式是用一些图片工具自动处理,GraphicsMagick和Imagemagick用来处理这个再合适不过了,他俩都可以用对图像进行旋转、裁剪、缩放、替换颜色;添加文本、水印、图形等常见操作,GraphicsMagick是从Imagemagick分支出来的,他俩有着几乎一样的API,可以在命令行工具中轻松操作。这里我用的是GraphicsMagick,他更轻便,易装易用。在Java、PHP、Nodejs等常见后端语言中可以用相关库轻松的操作GraphicsMagick的API。

后端以Nodejs为例。首先你需要在你的机器上安装GraphicsMagick。 然后npm install gm 模块就可以了。gm提供的接口非常友好,你只要
gm('/path/to/img.jpg')
.autoOrient()
.resize(240, 240)
.write('/path/to/new.jpg', function (err) {
if (err) ...
})


这样就已经完成了图片的自动修正方向和压缩尺寸的工作。


异步上传图片

传统提交表单方式放在今天已经不能忍了,XHR2中支持把文件放在Formdata对象中异步提交,只考虑移动端,就可以舍弃iframe之类的兼容方案了。核心代码这样:
var xhr = new XMLHttpRequest();
var formData = new FormData();
formData.append('file', input.files[0]);
xhr.open('POST', form.action);
xhr.send(formData);


XHR2中还可以通过process事件来监听进度,实现类似进度条的功能
xhr.onprogress = updateProgress;
xhr.upload.onprogress = updateProgress;
function updateProgress(event) {
if (event.lengthComputable) {
var percentComplete = event.loaded / event.total;
    ......
  }
}


用FormData发送的请求头中你的Content-Type 会变成这样 multipart/form-data; boundary=----WebKitFormBoundaryyqVkWF3PcCpAzZp9,如果上传时要附带参数也可以直接append到formData里。

然后Nodejs中可以用connect-busboy来接收文件,在express框架中大概是这样:
var express = require('express'),
http = require('http'),
fs = require('fs'),
busboy = require('connect-busboy'),
gm = require('gm');

var app = express();
app.use(busboy());
.....
app.post('/upload', function(req, res) {
req.busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
......
file.on('end', function () {
gm(filePath)
.autoOrient()
.thumbnail(200, 200)
.write(fullname, function(err){
if (err) return console.dir(arguments)
res.json({
......
});
}
)
});
file.pipe(fs.createWriteStream(filePath));
});
req.pipe(req.busboy);
});
......
app.listen(3001);


前端压缩图片

图片上传的主体工作算是完成了,不过现在手机随便拍张照片就是一两兆,wifi环境下不说,移动网络通过这方案上传照片就有点坑了。手机客户端中一般会先压缩图片再上传,Web中如何实现压缩后上传呢?

可以把图片读到canvas中,然后用canvas.toDataURL()接口输出画布的base64编码,再把base64编码转成Blob塞到Formdata里传到后端。这样即可以压缩图片减少流量,又可以在前端就修正图片旋转的问题。(Discuss:直接把base64传到后端是否可行呢,后面试一试)

canvasResize这个库已经把一切封装好了https://github.com/gokercebeci/canvasResize ,同时他依赖Exif.js 修正了因Orientation属性产生的旋转问题。前端主要的代码:
var file = input.files[0];
canvasResize(file, {
width: 300,
height: 0,
crop: false,
quality: 100,
callback: function(data, width, height) {
var blob = canvasResize('dataURLtoBlob', data);
var form = new FormData();
form.append('file',blob);
$.ajax({
type: 'POST',
url: server,
data: form,
contentType: false,
processData: false,
}).done(function (res) {
......
}).fail(function () {
......
}).always(function () {
......
});
}
});


Nodejs中代码可以参考前面的,继续用connect-busboy模块接收文件。

实际测试一下iOS没问题,Android 4 有些机型不行,貌似修改过file的Blob数据发到服务端的数据字节就会为0 这是安卓的bug https://code.google.com/p/android/issues/detail?id=39882
网上有人给出的解决方案是用FileReader把文件读出来,然后把整个二进制文件当请求发到服务端,这种方式要附带参数的话只能放url里了。
var reader = new FileReader();
reader.onload = function() {
$.ajax({
type: 'POST',
url: server,
data: this.result,
contentType: false,
processData: false,
beforeSend: function (xhr) {
xhr.overrideMimeType('application/octet-stream');
},
}).done(function (res) {
......
}).fail(function () {
......
}).always(function () {
......
});
};
reader.readAsArrayBuffer(file);


后端在接收这些数据时,会是一段一段的,我是用的拼接的方式处理
app.post('/upload', function(req, res) {
var imagedata = '';
req.setEncoding('binary');
req.on('data', function (chunk) {
imagedata += chunk
});
req.on('end', function (chunk) {
fs.writeFile(filePath, imagedata, 'binary', function(err){
if (err) throw err
res.json({
......
});
})
});
});


实测一下,稍低端的的安卓上有点卡,毕竟处理一张图片的运算量可不小,目测目前用前端压缩上传方案的不多,至少微博触屏版 (http://m.weibo.cn/) 就是把原始图片直接上传的,这种方式是否适合直接使用或者还有哪些可以优化的地方有待验证。

这里有一个完整的demo https://github.com/xiangpaopao/mobile-upload-demo 包括上面提到的两种方案

使用的话,依次安装GraphicsMagick > npm install > node app.js


参考

http://www.impulseadventure.com/photo/exif-orientation.html
http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/
http://stackoverflow.com/questions/13055915/ios-6-safari-image-upload-orientation-wrong
http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html
/article/4991337.html
https://github.com/fex-team/webuploader
/article/3788302.html
http://www.2fz1.com/post/node-js-canvas-base64/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: