最近要实现一个现场报名功能,采用B/S结构系统实现现场报名,现场采集用户信息录入系统。
前端使用bootstrap框架,后台使用ASP.NET MVC4。
刚开始只需要用户提供身份证,用读卡器读出身份证的信息自动填充到输入框中,然后补充其他信息保存即可。
身份证读卡器就是这货,USB接口。
做了个操作界面:
读身份证信息比较好办,调用读卡器接口对应的方法即可。
通过实验发现接口方法读取头像照片时,得到的是Base64格式的jpg图片,并且不包含“data:image/jpg;base64,”这段头信息,需要自己手工加上。图片尺寸为102*126像素,在图片上右击看属性:
提交报名信息时,后台将获取的Base64字符串转换成jpg并保存在指定路径下:
本来功能也就完成了,后来用户提出没带身份证报名的情况,需要增加从文件选择照片和现场用摄像头拍照两种方式,先感慨一下需求变更,还是要想办法实现功能。
分析后决定将文件选取和摄像头拍照用弹窗来操作,取得的图片直接展示在头像框处,并且为了和身份证读卡器读出的图片格式兼容,都设为Base64格式。这样就形成了统一思路,不论用什么方式获得头像照片(读身份证、文件选取、摄像头拍照),均以Base64格式展示在操作界面,这样提交到后台处理时方式是一样的。
================================= 分割一下 =================================
从文件中选取照片的处理
浏览照片选中时,用js控制在操作界面中展示选中照片,同时父窗口头像框处展示经过Base64处理过的图像。点击右上角关闭按钮或右下角确定按钮时,关闭当前弹窗。
元素"imgFile"为图片选择窗口中要展示的图片,"imgAvatar"为父窗口中的头像框,"hPhoto"为父窗口记录Base64内容的隐藏域,注意去掉了"data:image/jpeg;base64,"这段头信息,只保留图片数据内容。
此时头像框中图片格式与读身份证得到的图片格式一样,后台保存方法都不用改。即使选择的图片格式不是jpg的,仍然会在后台代码中自动转换成jpg格式。
=================================再分割一下=================================
摄像头拍照的处理稍微麻烦一下,主要在于拍照的图片是横版的,但系统要求的头像图片是竖版的,需要进行裁切。
摄像头就是普通那种USB接口的摄像头,免驱动的。
网页调用摄像头拍照有多种方案,对比后采用的是flash方式。
项目中引用需要的资源文件:
页面中调用:
<script src="/assets/js/jquery-1.8.3.min.js" type="text/javascript"></script>
<script src="/assets/js/webcam/jquery.webcam.js"></script>
思路是这样的:flash显示摄像头实时画面,点击“拍照”按钮时捕获当前画面,然后按比例裁切中间一部分图像展示在画布中。
摄像头实时画面设定为320*240像素,按照头像实际尺寸计算,应该截取拍照画面中间的194*240区域。
为了方便操作者预览,在摄像头实时画面的左右两侧增加了一个半透明区域,意思是这两块区域要删掉,只保留中间一部分。
弹窗中左侧是摄像头画面,下方有“拍照”按钮,拍照的图片自动裁切后显示在右侧,父窗口同步显示拍照结果。
左右两侧的半透明区域是用css控制的div透明度,注意flash调用摄像头的z-index数值,要将半透明区域叠加到flash之上才行。
摄像头画面只保留中间的194*240区域的内容,因此拍照后要将原始图片从(63,0)位置开始裁切大小为194*240区域的图像,js代码是image = ctx.getImageData(63, 0, 194, 240);
显示摄像头画面和抓拍画面的html:
其中,id为“webcam”的div在页面加载时会append上flash,由flash调用摄像头。
=================================待改进地方 =================================
1、程序中bootstrap框架采用ACE1.3.3版本,打开对话窗口时,是将弹窗文件解析后附加在本页中,因此会受本月的脚本和代码影响。从文件选取图片功能用默认的对话窗口可以实现功能,但摄像头拍照功能则不能生效,我只能用模态窗口来实现效果,并且在点击“拍照”按钮时将抓拍的图片回传给父窗口。
2、因客户只使用IE浏览器,并且版本也固定,因此可以不用考虑过多兼容问题。身份证读卡器使用Activex控件注册,如果是火狐或谷歌浏览器要加载不同的控件。另外实时摄像头画面两侧半透明区域只在IE下起作用,其他浏览器要再处理。
=================================总结=================================
1、通过三种不同方式获取头像信息,传到后台保存时已经处理成同样的格式,这也是代码分离原则的体现。以头像框为中转站,前台js各种处理,达到同样格式的Base64图片;后台获取图片字符串,统一保存成jpg格式。也相当于调用后台的一个方法时,通过各种手段将传入参数统一好格式,服务器端方法只需要接收到参数内容处理即可,不需要再做更多情况判断。
2、不同版本的bootstrap有一定的浏览器兼容问题,特别是jquery版本。某些时候使用原生js是更好的选择。
3、花哨的功能尽量少,用最简单的方式实现功能,稳定性是最好的。
附:代码文件
父页面部分代码
<input type="hidden" id="hPhoto" name="hPhoto" />
<input type="file" id="picFile" name="picFile" οnchange="selectPicFile()" style="display:none;" />
<img id="imgAvatar" alt="" src="/assets/avatars/default.jpg" width="102" height="126" />
<div class="col-sm-4 align-left">
<a href="#" role="button" οnclick="CapturePicture()"><i class="fa fa-camera fa-1x" title="从摄像头拍照"> 拍照</i></a>
<br />
<a href="FilePicture" role="button" data-toggle="modal" data-target="#FilePicture"><i class="fa fa-folder-open fa-1x" title="从文件中选择"> 文件</i></a>
</div>
<div id="FilePicture" class="modal fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
<script type="text/javascript">
jQuery(function ($) {
$("#FilePicture").on("hidden.bs.modal", function () {
$(this).removeData("bs.modal");
});
function CapturePicture() {
return window.showModalDialog("CapturePicture", GetResult, "dialogWidth=620px,dialogHeight=400px");
}
function GetResult(imageContent) {
document.getElementById('imgAvatar').src = imageContent;
document.getElementById('hPhoto').value = imageContent.substring(imageContent.indexOf(";base64,") + 8);
}
});
</script>
文件选择子页面(FilePicture)
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta charset="utf-8" />
<title></title>
<script type="text/javascript">
function showImage(file) {
var imageContent = '';
if (!file.files || !file.files[0]) {
return;
}
var reader = new FileReader();
reader.onload = function (evt) {
imageContent = evt.target.result;
document.getElementById('imgFile').src = imageContent;
document.getElementById('imgAvatar').src = imageContent;
document.getElementById('hPhoto').value = imageContent.substring(imageContent.indexOf(";base64,") + 8);
}
reader.readAsDataURL(file.files[0]);
}
</script>
</head>
<body class="no-skin">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 class="smaller lighter blue no-margin">选择照片</h3><h5>(尺寸:102*126像素或等比例)</h5>
</div>
<div class="modal-body">
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<td align="left">
<input type="file" id="picFile" name="picFile" οnchange="showImage(this)" />
</td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td>
<img id="imgFile" style="height:220px;" alt="" />
</td>
</tr>
</table>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal" data-bb-handler="success" class="btn btn-sm btn-success">
<i class="ace-icon fa fa-check"></i> 确定
</button>
</div>
</body>
</html>
摄像头抓拍子页面(CapturePicture)
@{
Layout = null;
}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>拍照</title>
<style type="text/css">
#webcam {
width: 320px;
}
#webcam {
position:relative;
}
object {
display:block; /* HTML5 fix */
position:relative;
z-index:100;
}
.div1{
width:320px;
height:240px;
position:relative;
border:1px solid #cccccc;
}
.div2{
width:63px;
height:240px;
position:absolute;
left:0;
top:0;
background:#000000;
z-index:9999;
opacity:0.3;
filter:alpha(opacity:30);
moz-opacity:0.3;
}
.div3{
width:63px;
height:240px;
position:absolute;
right:0;
top:0;
background:#000000;
z-index:9999;
opacity:0.3;
filter:alpha(opacity:30);
moz-opacity:0.3;
}
</style>
<script src="/assets/js/jquery-1.8.3.min.js" type="text/javascript"></script>
<script src="/assets/js/webcam/jquery.webcam.js"></script>
<script type="text/javascript">
var pos = 0;
var ctx = null;
var cam = null;
var saveCB;
var image = [];
$(document).ready(function () {
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
ctx = document.getElementById("canvas").getContext("2d");
ctx.clearRect(0, 0, 320, 240);
var img = new Image();
img.src = "";
img.onload = function () {
ctx.drawImage(img, 0, 0);
}
image = ctx.getImageData(0, 0, 320, 240);
}
if (canvas.toDataURL) {
ctx = canvas.getContext("2d");
image = ctx.getImageData(63, 0, 194, 240);
saveCB = function (data) {
var col = data.split(";");
var img = image;
for (var i = 63; i < 257; i++) {
var tmp = parseInt(col[i]);
img.data[pos + 0] = (tmp >> 16) & 0xff;
img.data[pos + 1] = (tmp >> 8) & 0xff;
img.data[pos + 2] = tmp & 0xff;
img.data[pos + 3] = 0xff;
pos += 4;
}
if (pos >= 4 * 194 * 240) {
ctx.putImageData(img, 0, 0);
pos = 0;
}
};
} else {
saveCB = function (data) {
image.push(data);
pos += 4 * 194;
if (pos >= 4 * 194 * 240) {
pos = 0;
}
};
}
jQuery("#webcam").webcam({
mode: "callback",
swffile: "/assets/js/webcam/jscam_canvas_only.swf",
onCapture: function () {
webcam.save();
var canvas = document.getElementById("canvas")
var imageContent = canvas.toDataURL("image/png")
var callBack = window.dialogArguments;
if (callBack != undefined && callBack != null) {
callBack(imageContent);
}
},
onSave: saveCB,
debug: function (type, string) {
jQuery("#status").html(type + ": " + string);
},
onLoad: function () {
var cams = webcam.getCameraList();
for (var i in cams) {
jQuery("#cams").append("<li>" + cams[i] + "</li>");
}
}
});
});
</script>
</head>
<body class="no-skin" style="margin:10px;">
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<td height="56"><ul id="cams"></ul></td>
<td width="50"> </td>
<td><p id="status"></p></td>
</tr>
<tr style="height:3px;">
<td><hr /></td>
<td></td>
<td><hr /></td>
</tr>
<tr>
<td colspan="3"> </td>
</tr>
<tr>
<td align="center">
摄像头画面
</td>
<td></td>
<td align="center">
拍照结果
</td>
</tr>
<tr>
<td align="center">
<div id="webcam" class="div1">
<div class="div2"></div>
<div class="div3"></div>
</div>
</td>
<td></td>
<td align="center">
<div>
<canvas id="canvas" width="194" height="240"></canvas>
</div>
</td>
</tr>
<tr>
<td align="center"><a href="javascript:webcam.capture();void(0);">拍照</a></td>
<td></td>
<td></td>
</tr>
<tr>
<td colspan="3" height="20"> </td>
</tr>
<tr>
<td colspan="3" height="50" align="center" style="background-color:#cccccc"><input type="button" value="完成" οnclick="window.close();" /></td>
</tr>
</table>
</body>
</html>