如何上传照片到开放性实践活动平台上
阅读(466)
2017-04-04 06:00:05
点附件没有反应
提问者:蝴蝶为谁开~
悬赏分:4分
评论
-
雪晴
2017-06-07
其他链接: http://tgideas.qq.com/webplat/info/news_version3/804/808/811/m579/201409/278736.shtml 从iOS 6+、Android 3+开始 (来源http://mobilehtml5.org/),移动端可以通过网页中的来拍照上传或是上传相册中的照片。 不过从图片上传到服务器后可能会遇到图片莫名其妙旋转的问题,如图 default 一些设备在拍照时明明是竖着拍的(右),传到服务端后从图片查看器中看到的是横着的(左),而在一些图片处理工具或是浏览器中通过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定义了一个标准的方向参数,只要读图的软件都来遵守规则,加载时候读取图片方向,然后做相应的旋转即可。这样既可以达到快速成像的目的,又能达到正确的显示。 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赞0