移动端Web上传图片实践

来源:互联网 发布:lol画面卡顿网络 编辑:程序博客网 时间:2024/05/20 20:17

原文链接

其他链接:

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">来拍照上传或是上传相册中的照片。 不过从图片上传到服务器后可能会遇到图片莫名其妙旋转的问题,如图

default

一些设备在拍照时明明是竖着拍的(右),传到服务端后从图片查看器中看到的是横着的(左),而在一些图片处理工具或是浏览器中通过http协议看到的却是正常的,原因是在照片的exif中的Orientation属性控制了照片的旋转方向。

//Exif 信息示例Exif.Image.Make                              Ascii       6  CanonExif.Image.Model                             Ascii      20  Canon PowerShot S40Exif.Image.Orientation                       Short       1  top, leftExif.Image.XResolution                       Rational    1  180Exif.Image.YResolution                       Rational    1  180Exif.Image.ResolutionUnit                    Short       1  inchExif.Image.DateTime                          Ascii      20  2003:12:14 12:01:44Exif.Image.YCbCrPositioning                  Short       1  Centered......

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

orient_flag2

修正图片方向的问题

为了图片显示不出问题,我们得修改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
  • http://www.cnblogs.com/smileEvday/p/imageInfo.html
  • https://github.com/fex-team/webuploader
  • http://elf8848.iteye.com/blog/382528
  • http://www.2fz1.com/post/node-js-canvas-base64/
0 0
原创粉丝点击