场景:需要拍照或从相册选取多张图片上传。
遇到问题:5+API中plus.uploader管理对象完成上传功能后发现如果上传图片大于1000kb,图片上传速度减慢,图片大于2M则上传失败,但接口返回结果状态为200,也就是上传成功。现需要压缩图片上传。
接口地址:
https://www.html5plus.org/doc/zh_cn/uploader.html#plus.uploader.Upload
API坑:
在plus.camera管理摄像头的对象中,supportedImageResolutions属性( 字符串数组,摄像头支持的拍照分辨率) 能够获取手机硬件得所有分辨率参数,返回字符串数组。但是该属性无法设置摄像头使用的分辨率,故而设置无效,拍出来的照片永远是你手机最大分辨率。这就导致了拍出来的照片一般都是2-3M居多
官方给的例子中设置了该参数:
supportedImageResolutions
字符串数组,摄像头支持的拍照分辨率
说明:
Array 类型 只读属性
属性类型为String[],若不支持此属性则返回空数组对象。摄像头支持的拍照图片分辨率字符串形式“WIDTH*Height”,如“400*800”;如果支持任意自定义分辨率则“*”。
平台支持:
Android (支持)
iOS (不支持): 返回空数组对象
示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Camera Example</title>
<script type="text/javascript">
// 扩展API加载完毕后调用onPlusReady回调函数
document.addEventListener( "plusready", onPlusReady, false );
// 扩展API加载完毕,现在可以正常调用扩展API
function onPlusReady() {
var cmr = plus.camera.getCamera();
alert( "Camera supperted image resolutions: " + cmr.supportedImageResolutions );//无效
}
</script>
</head>
<body>
上例中官方给出的是添加的resolution属性,然而在官方文档中的CameraOption却没有这个属性,如下
CameraOption
JSON对象,调用摄像头的参数
interface CameraOption {
attribute String filename;
attribute String format;
attribute String index;
attribute PopPosition popover;
}
属性:
filename: (String 类型 )拍照或摄像文件保存的路径
可设置具体文件名(如"_doc/camera/a.jpg");也可只设置路径,以"/"结尾则表明是路径(如"_doc/camera/")。如未设置文件名称或设置的文件名冲突则文件名由程序程序自动生成。
format: (String 类型 )拍照或摄像的文件格式
可通过Camera对象的supportedImageFormats或supportedVideoFormats获取,如果设置的参数无效则使用系统默认值。
index: (String 类型 )拍照或摄像默认使用的摄像头
拍照或摄像界面默认使用的摄像头编号,1表示主摄像头,2表示辅摄像头。
平台支持
Android - 2.2+ (不支持): 暂不支持设置摄像头,忽略此属性值
iOS - 4.3+ (支持)
popover: (PopPosition 类型 )拍照或摄像界面弹出指示区域
对于大屏幕设备如iPad,拍照或摄像界面为弹出窗口,此时可通过此参数设置弹出窗口位置,其为JSON对象,格式如{top:"10px",left:"10px",width:"200px",height:"200px"},默认弹出位置为屏幕居中。
平台支持
Android - ALL (不支持): 暂不支持设置摄像头,忽略此属性值
iOS - 5.0+ (支持): 仅iPad设备支持此属性,iPhone/iTouch上忽略此属性值
所以这是第一个,这样我们就无法通过控制摄像头的分辨率来保证拍照获取的照片都比较小。
因此,希望通过压缩图像处理,查看官方文档(此处为API第二个坑)
compressImage,//该接口是plus.zip.compressImage
图片压缩转换
void plus.zip.compressImage( options, successCB, errorCB);
说明:
可用于图片的质量压缩、大小缩放、方向旋转、区域裁剪、格式转换等。
参数:
options: ( CompressImageOptions ) 必选
图片压缩转换的参数
successCB: ( CompressImageSuccessCallback ) 可选
图片压缩转换操作成功回调,操作成功时调用。
errorCB: ( ZipErrorCallback ) 可选
图片压缩转换操作失败回调,操作失败时调用。
以上亲测无效,提示找不到这个Function,在官方提供的问答社区中资料较少,有人也表示找不到这个方法,但也有人问 压缩中出现的问题,不过年代久已,是15年的问题,至今好像都没处理,问答社区不够活跃。
经过查询相关资料,最终使用canvas来压缩处理图像。以下是使用方法:
//添加img和canvas标签,一个用于存放原图片,一个用于drawImage并获取压缩后的数据
<canvas id="canvas" style="display: none;"></canvas>
<img id="source" src="" style="display: none;">
function gallery_get_base64(files)//参数:原图片的地址[数组],该地址可以通过gallery.pick返回或camera.captureImage中的success回调函数获得
{
var cur_file=files.pop();//注意*压缩过程中,因为传入的是图片路径数组,必须用递归方式去压缩,否则如果通过for循环的话,还未压缩完成一张,for循环已经结束,最终只会得到最后一张图片
plus.nativeUI.showWaiting('加载中');
var canvas,source,dataUrl;
canvas = document.getElementById('canvas');
source = document.getElementById('source');
dataUrl;//存储画在画布上的数据
source.onload=function(){//当原图片加载好时触发
var width = source.width;
var height = source.height;
var context = canvas.getContext('2d');
// draw image params
var sx = 0;
var sy = 0;
var sWidth = width;
var sHeight = height;
var dx = 0;
var dy = 0;
var dWidth = width;
var dHeight = height;
var quality = 0.2;//图片的质量范围为0-1,设置成0.2的话3M图片大概能压缩成250-290k左右
canvas.width = width;
canvas.height = height;
/*
剪切图像,并在画布上定位被剪切的部分:
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
参数 描述
img 规定要使用的图像、画布或视频。
sx 可选。开始剪切的 x 坐标位置。
sy 可选。开始剪切的 y 坐标位置。
swidth 可选。被剪切图像的宽度。
sheight 可选。被剪切图像的高度。
x 在画布上放置图像的 x 坐标位置。
y 在画布上放置图像的 y 坐标位置。
width 可选。要使用的图像的宽度。(伸展或缩小图像)
height 可选。要使用的图像的高度。(伸展或缩小图像)
*/
context.drawImage(source, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
/*
HTMLCanvasElement.toDataURL() 方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi。
如果画布的高度或宽度是0,那么会返回字符串“data:,”。
如果传入的类型非“image/png”,但是返回的值以“data:image/png”开头,那么该传入的类型是不支持的。
Chrome支持“image/webp”类型。
语法
canvas.toDataURL(type, encoderOptions);
参数
type 可选
图片格式,默认为 image/png
encoderOptions 可选
在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。
返回值
包含 data URI 的DOMString。
*/
dataUrl = canvas.toDataURL('image/jpeg', quality); //此处第二个参数为0.2,这样裁剪后只有两三百K
console.log(dataUrl);
$('#images_show ul').append('<li><img width="88px" height="88px" src="' + dataUrl + '"/></li>');
if(files.length>0)
{
// 递归,否则无法全部压缩
gallery_get_base64(files);
}
else{
plus.nativeUI.closeWaiting();
}
}
source.src=cur_file;
}
在做完压缩操作后,我的DOM里面就有了IMG的列表,里面的src是canvas.todataURL返回的数据,该数据是通过base64编码的,此时获取到这个数据,我也改变了我的上传文件方式,并非通过uploader对象的addFile来上传了。(因为该接口的参数必须是文件的路径)而是直接通过addDATA完成任务,因为现在已经是一串编码数据,再此还要注意的是,datatoURL返回的字符串头部是有识别的字符串的,开头那一部分我们在后台接收的时候并不需要,那也不是base64编码的结果,只是识别作用,所以我们可以把前面23个字符去掉 var base64=data.substr(23);然后把这个base64传到后台
如果是多张图片则循环把img src里面的字符串全部截取下来,通过upload对象的addDATA添加上去,注意该接口的参数key是不允许重复的否则会失败,注意区分
在后台接收到base64数据后,解密并写入文件,(**注意**传入后台的base64数据中‘+’号会被替换成空格,所以要将空格全部替换回来)下面是PHP例子:
<?php
header("Content-Type: text/html; charset=utf-8");
$file_number=$_POST['file_number'];
$key=$_POST['key'];//标记,因为上传的时候必须保证key是不一致的,要获取这个数据就通过某一特定标记+i(循环)获取
for($i=0;$i<$file_number;$i++)
{
$cur_file=$key.$i;//假设key是 file 则,循环获取的是 file0 file1 file2 ....
$img = str_replace(' ', '+', $_POST[$cur_file]);//注意因为传入到后台的数据+号会被替换成空格,故而此处要转换回来,base64编码结果有+号
$img_decode64 = base64_decode($img);//base64解码,
$f = fopen('upload/'.$key.$i.'.jpg', 'w+');//写入图片
fwrite($f, $img_decode64);
fclose($f);
}
?>