HTML5下Base64圖片處理結合攝像頭抓拍裁切

最近要實現一個現場報名功能,採用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" onchange="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"  onclick="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">&times;</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" onchange="showImage(this)" />
                    </td>
                </tr>
                <tr>
                    <td>&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</td>
        </tr>
        <tr>
            <td colspan="3" height="50" align="center" style="background-color:#cccccc"><input type="button" value="完成" onclick="window.close();" /></td>
        </tr>
    </table>


</body>
</html>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章