Flash WebCam 拍照存檔
要在 Flash 中,使用 WebCam 拍照,僅需透過 BitmapData 即可擷取畫面,然後問題在於如何將這張畫面存成檔案?由 Server 程式來做?Flash 如何將資料傳給 Server 程式?在 Flash 8 的時候,我曾經將 BitmapData 的所有像素讀出來串成一大串的字串送給後端,現在,則有更容易的作法了。
有人用 AS3 寫好 Flash 版本的 PNG / JPG encoder,可以將 BitmapData 變成 PNG / JPG 格式的 ByteArray,然後只要我們能將 ByteArray 送到後端程式作儲存即可。資料傳送的部份也有人是利用 URLRequest / URLLoader 來傳送 stream 資料到後端,然後由 PHP / ASP / Java 接收儲存。更讚的是,有人寫好一個 function 可以直接將 ByteArray 資料打包成 HTTP upload 的規格,這樣一來後台程式就跟接收一般由 HTML 
<HTML>
<p>Demo MultipartRequest Upload Servlet</p>
<FORM ACTION="FileUpload" METHOD=POST ENCTYPE="multipart/form-data">
What is your name? <INPUT TYPE=TEXT NAME=submitter> <BR>
Which file to upload? <INPUT TYPE=FILE NAME=file1> <BR>
Which file to upload? <INPUT TYPE=FILE NAME=file2> <BR>
Which file to upload? <INPUT TYPE=FILE NAME=file3> <BR>
<INPUT TYPE=SUBMIT>
</FORM>
</HTML>
然後,Java Servlet 的部份,有利用到 com.oreilly.servlet 的上傳類別 (http://www.servlets.com/cos/) 來處理:
package idv.ben.fileUpload;
import java.io.*;
import java.util.Enumeration;
import javax.servlet.*;
import javax.servlet.http.*;
import com.oreilly.servlet.MultipartRequest;
public class FileUpload extends javax.servlet.http.HttpServlet implements
javax.servlet.Servlet {
static final long serialVersionUID = 1L;
private String dirName;
public void init(ServletConfig config) throws ServletException {
super.init(config);
dirName = config.getInitParameter("uploadDir");
if (dirName == null) {
throw new ServletException("Please supply uploadDir parameter");
}
}
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
response.setContentType("text/plain");
out.println("Demo Upload Servlet using MultipartRequest");
out.println();
try {
MultipartRequest multi = new MultipartRequest(request, dirName,
10 * 1024 * 1024, "ISO-8859-1");
out.println("PARAMS:");
Enumeration params = multi.getParameterNames();
while (params.hasMoreElements()) {
String name = (String) params.nextElement();
String value = multi.getParameter(name);
out.println(name + "=" + value);
}
out.println();
out.println("FILES:");
Enumeration files = multi.getFileNames();
while (files.hasMoreElements()) {
String name = (String) files.nextElement();
String filename = multi.getFilesystemName(name);
String originalFilename = multi.getOriginalFileName(name);
String type = multi.getContentType(name);
File f = multi.getFile(name);
out.println("name: " + name);
out.println("filename: " + filename);
out.println("originalFilename: " + originalFilename);
out.println("type: " + type);
if (f != null) {
out.println("f.toString(): " + f.toString());
out.println("f.getName(): " + f.getName());
out.println("f.exists(): " + f.exists());
out.println("f.length(): " + f.length());
}
out.println();
}
} catch (IOException lEx) {
this.getServletContext().log(lEx, "error reading or saving file");
}
}
}
另外,也稍微注意一下 web.xml 的部份,有將存檔資料夾設定在 servlet 的初始化參數中:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>
FileUpload</display-name>
<servlet>
<description>
</description>
<display-name>
FileUpload</display-name>
<servlet-name>FileUpload</servlet-name>
<servlet-class>
idv.ben.fileUpload.FileUpload</servlet-class>
<init-param>
<param-name>uploadDir</param-name>
<!-- Current dir for the server process, replace with /tmp or c:\temp maybe -->
<param-value>C:\temp</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>FileUpload</servlet-name>
<url-pattern>/FileUpload</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>
以上,其實沒啥特別的,這跟一般的接收上傳的程式沒啥兩樣,若是你用 PHP 或其他語言的話,就根本不用理會上面這段。
接下來才是重點,我以 Flex 的 MXML 專案來開發這個 WebCam 拍照介面,並撰寫傳送資料到後台的程式:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="onAppInit()">
<mx:Script>
<![CDATA[
import mx.core.UIComponent;
import net.kaourantin.JPEGEncoder;
import com.jooce.net.uploadFile;
private var camera:Camera;
private var video:Video;
private var bd:BitmapData;
private function onAppInit():void{
camera = Camera.getCamera();
if(camera!=null){
video = new Video(320, 240);
video.attachCamera(camera);
var holder:UIComponent = new UIComponent();
holder.addChild(video);
panelCamera.addChild(holder);
}
}
private function takePhoto():void{
if(video!=null){
bd = new BitmapData(320, 240);
var bmp:Bitmap = new Bitmap(bd);
var holder:UIComponent = new UIComponent();
holder.addChild(bmp);
panelPhoto.addChild(holder);
bd.draw(video);
}
}
private function uploadPhoto():void{
if(bd!=null){
var jpegEnc:JPEGEncoder = new JPEGEncoder(75);
var jpegDat:ByteArray = jpegEnc.encode(bd);
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, onURLLoaderComplete);
loader.addEventListener(IOErrorEvent.IO_ERROR, onURLLoaderFailure);
uploadFile(loader, 'http://localhost:9001/FileUpload/FileUpload', jpegDat, 'photo.jpg');
}
}
private function onURLLoaderComplete(evt:Event):void{
trace("onURLLoaderComplete");
}
private function onURLLoaderFailure(evt:IOErrorEvent):void{
trace("onURLLoaderFailure");
}
]]>
</mx:Script>
<mx:Panel width="340" height="280" layout="absolute" id="panelCamera" left="10" top="10" title="Camera">
</mx:Panel>
<mx:Panel width="340" height="280" layout="absolute" id="panelPhoto" top="10" left="360" title="Photo">
</mx:Panel>
<mx:Button label="Take Photo" id="btnTakePhoto" left="260" top="300" click="takePhoto()"/>
<mx:Button label="Upload Photo" id="btnUploadPhoto" left="600" top="300" click="uploadPhoto()"/>
</mx:Application>
不難看出,重點在於 JPEGEncoder 與 uploadFile。
JPEGEncoder 可以參考以下網址:
http://www.kaourantin.net/2005/10/more-fun-with-image-formats-in-as3.html
http://www.yov408.com/html/codespot.php?gg=47
http://www.kaourantin.net/source/JPEGEncoder.as
不過要小心的是,因為作者開發這個類別的時間比較早,所以有些部份可能會需要作一點點微調,像是 Flex 好像不允許一個 *.as 中有兩個 class,以及 flash.util-->flash.utils,與 Void-->void 等等。
另外,uploadFile() 的部份,可以參考以下網址:
http://www.jooce.com/blog/?p=143
http://www.jooce.com/blog/wp%2Dcontent/uploads/2007/06/uploadFile.txt
作者是寫成 package level 的 function,這個小地方稍微注意一下即可,用起來倒是還挺方便的。
16 意見:
謝謝分享
我的作法一樣是用AS3 JPGEncode
只是用php和amfphp來接值
來試試你的方法好了..3Q
你好邦邦,我正在做Flash WebCam 拍照存檔后台是php.但是我不是个flash高手.你能不能把你的软体Flash WebCam 发给我分享一下.先谢谢了
我的邮箱maxime_fr@hotmail.com
抱歉,這個範例我是用 FLEX 開發的,完整原始檔都在文章中,你可以自由取用這篇文章中的資料!
邦邦老師您好~
我是AS3的新手~
請教一個問題~怎麼讓視訊讀到的畫質變好呢?我看了一下~FLASH CS3 中的說明~沒有提到相關的問題耶~?麻煩您指導一下~ 謝謝
哈~邦邦老師我是樓上的~~
我找到myCam.setMode()的方法了~
哈~學藝不精~~~我會好好努力的~~
邦邦老師~這裡真是個好地方~~~學到很多東西~^^加油~
邦邦老師 有一個問題外的問題
最近剛開始用Blogger的系統
關於你文章裡面那些程式碼的區塊要怎麼弄
是單純的<div>或<span>嘛
還是是用表格然後將一些保留的字元
例如: < → <
最近在使用的時候出現了一些問題
這個系統好像很方便又好像跟我以前台灣一些Blog服務者的介面設計的習慣有很大的不同.
< → & l t ;
請問一下..
我在As3中要使用JPGEncode時..編譯都無法成功..
都會說變數重覆宣告..
不過我看log...那些所謂的重覆宣告的變數..大多是for迴圈中的i耶..
有什麼地方要修正的嗎?
那就不要重複宣告啊!
var i:Number
for(i=0; i<5; i++){
trace(i)
}
for(i=5; i<10; i++){
trace(i)
}
如果有網友的平台跟我 demo 的不一樣,我相信一定會的(因為 Java 有點硬,Flex 也是!)。
譬如你若是用 PHP 當作後台的話,只要你先學會如何用 PHP 寫上傳檔案的接收程式即可,像我上面的例子中,也至少先寫好 html 前端與 Java 後端,然後才將前端改用 flash。
若是你的前端不使用 flex 而是使用 flash ide 開發的話,那麼只要注意我程式中的 takePhoto() 將畫面存成 BitmapData,以及 uploadPhoto() 透過 JPEGEncoder 將 BitmapData 轉成 ByteArray,然後用 URLLoader 將 ByteArray 送給後台的上傳檔案接收程式即可。
邦邦前輩您好:
我照你的方法做,不過我因為不用存檔,單單只需要照相定格的動作,所以我把動作都放到一個按鈕的函式裡,可是很奇怪的是,為什麼照下來後,會變得很小張然後顯示在同塊區域的左上角,是個像素有關嗎?!我該怎麼修改呢?
不好意思,新手剛接觸,問題比較多,還請前輩多多包涵,謝謝~
可以注意一下 matrix 的用法
在製作BitmapData前,進行了mc的縮放
http://blog.xuite.net/ben19770209/ASBeginner/5255724
matrix在mc的beginBitmapFill之中的用處
http://blog.xuite.net/ben19770209/ASBeginner/5258900
因為,有可能你的原圖本來是小圖,卻被放大呈獻,所以截圖的時候,會抓到他原本的圖片大小,也就是小圖~這時候就可以透過 matrix 來控制截圖的縮放,或是簡單一點的話,你的原圖外面多加一層 sprite / mc,這樣你要截圖時就抓外面這個 sprite / mc 即可。
總之,原圖的縮放,對截圖出來的結果是不會有影響的,這反而使得開發者必須將截圖結果作些調整以配合"原圖縮放後的呈現"。
邦邦前輩好:謝謝您給我的指教,正在研究怎麼用。不過我剛剛突然發現一個快速照相的方法,就是如果在不需要存檔的情況下,其實不用設BitmapData和Bitmap,只要再按鈕的地方,寫上vid.attachCamera(null);視訊影像就會停在那格了,這算是比較投機的方法吧??
無論如何,先謝謝前輩忙中抽空指教,小弟受惠了~
說到工作~我知道奧美、AGENDA安捷達 都很缺flash技術人員,至於薪資上,建議你把你分享多年blog上flash技術問題list給他們看看~還有那磨多同行的朋友們,相信你的身價不凡~
不過,廣告公司的creative directer或AD都只會平面設計,非常渴望flash專才卻也對flash技術人的專精與否很無知...
good luck~
您好,邦邦,看了你這篇文章讓我受用不少,可是我有一個大疑問想請問您,這個範例是儲存上傳單張照片,如果是要一次儲存多張照片呢?我嘗試用一個array去把每次轉出來的bytearray放進去,一開始似乎是可行的,可是大概跑了60張圖後就出現一個"script 已經執行超過預設的 15 秒逾時時段"的錯誤訊息,請問有更好得方法嗎?還是非得一張一張的將bytearray上傳呢?
可以找一個做 zip 的 as3 library 試試看~
張貼意見