четверг, 23 февраля 2012 г.

FLEX: основы работы с веб-камерой

Всё началось с того, что возникло неодолимое желание увидеть свою физиономию на веб-странице. Для того, чтобы осуществить эту мечту, разумеется, проще всего воспользоваться флексом.

Сам по себе процесс подключения к камере тривиален. Добавляем компонент spark.components.VideoDisplay:
<s:VideoDisplay id="videoDisplay"
creationComplete="videoDisplay_creationComplete();"
x="40"
y="44"
width="320"
height="240"
/>

и цепляем в скрипте к нему камеру:
private var camera:Camera = null;

private function videoDisplay_creationComplete():void
{
camera = Camera.getCamera();
if (!camera)
return;
camera.setQuality(0,100);
var localCam:Video = new Video(320,240);
videoDisplay.addChild(localCam);
localCam.attachCamera(camera);
}


Правда, на этом дело не окончилось. Аппетит, пришедший во время еды, потребовал добавить возможность делать снапшоты. Это оказалось тоже несложно. Добавляем кнопку:
<s:Button id="button"
label="Сделать фотографию"
click="videoDisplay_snapShot();"
/>

список, в которм будем показывать снимки:
<s:List id="mylist" horizontalCenter="1" width="100%" height="77" itemRenderer="SnapshotRenderer">
<s:dataProvider>
<mx:ArrayList/>
</s:dataProvider>
<s:layout>
<s:HorizontalLayout horizontalAlign="center" verticalAlign="middle"/>
</s:layout>
</s:List>

и в обработчике нажатия кнопки пишем следующее:
private function videoDisplay_snapShot():void
{
if (!camera)
return;
if (camera.muted)
return;
try
{
var snapshot:BitmapData = ImageSnapshot.captureBitmapData(videoDisplay);
var bitmap:Bitmap = new Bitmap(snapshot);
if (3 < mylist.dataProvider.length)
mylist.dataProvider.removeItemAt(0);
mylist.dataProvider.addItem(bitmap);
}
catch (error:*)
{
Alert.show(error.message);
}
}


В окончательном виде получился вот такой миленький проект:

1. Файл SnapshotRenderer.mxml - отвечает за отрисовку снимков экрана

<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="100" height="75">

<mx:Image source="{this.data}" width="100%" height="100%"/>

</s:ItemRenderer>


2. Файл main.mxml - основной файл проекта
<?xml version="1.0" encoding="utf-8"?>
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
frameRate="100"
creationComplete="init()"
>

<s:layout>
<s:VerticalLayout horizontalAlign="center" verticalAlign="middle" paddingLeft="5" paddingRight="5" paddingTop="5" paddingBottom="5"/>
</s:layout>

<s:Group>

<!-- рисуем рамку вокруг области с видеоизображением с камеры -->
<s:Image source="@Embed(source='frame.jpg')"/>

<!-- область с видеоизображением с камеры -->
<s:VideoDisplay id="videoDisplay"
x="40"
y="44"
width="320"
height="240"
/>
</s:Group>

<s:Button id="button"
label="Сделать фотографию"
click="videoDisplay_snapShot();"
/>

<!-- галерея снимков, присланных с сервера -->
<s:List id="mylist" horizontalCenter="1" width="100%" height="77" itemRenderer="SnapshotRenderer">
<s:dataProvider>
<mx:ArrayList/>
</s:dataProvider>
<s:layout>
<s:HorizontalLayout horizontalAlign="center" verticalAlign="middle"/>
</s:layout>
</s:List>

<fx:Script>
<![CDATA[
import mx.graphics.ImageSnapshot;
import mx.graphics.codec.JPEGEncoder;
import mx.utils.Base64Encoder;
import mx.controls.Alert;

private var camera:Camera = null;

// функция, подключающая камеру
private function videoDisplay_creationComplete():void
{
try
{
// в этот момент на экран выскочит окно с предупреждением безопасности
// если пользователь откажется от подключения камеры, это можно будет потом узнать,
// проверив свойство camera.muted
camera = Camera.getCamera();

if (camera)
{
camera.setQuality(0,100);
var localCam:Video = new Video(320,240);

// маленькая хитрость, чтобы получить зеркальное отображение видео с камеры
var WIDTH:int = localCam.width;
var ma:Matrix=new Matrix();
ma.a = -1;
ma.tx = WIDTH;
localCam.transform.matrix = ma;

videoDisplay.addChild(localCam);
localCam.attachCamera(camera);
}
else
{
Alert.show("Нет доступа к веб-камере");
}
}
catch(error:*)
{
Alert.show(error.message);
}
}

// класс, служащий для передачи снимка экрана на сервер и получения в ответ нескольких
// последних изображений - для отображения в галерее
private var http:HTTPServiceEngine = new HTTPServiceEngine();

private function videoDisplay_snapShot():void
{
// проверка наличия камеры
if (!camera)
return;

// проверка, не отказался ли пользователь подключить камеру
if (camera.muted)
return;

try
{
var snapshot:BitmapData = ImageSnapshot.captureBitmapData(videoDisplay);

// кодируем снимок экрана в base64 и отправляем его на сервер
var jpg:JPEGEncoder = new JPEGEncoder();
var ba:ByteArray = jpg.encode(snapshot);
var b64encoder:Base64Encoder = new Base64Encoder();
b64encoder.encodeBytes(ba);
var b64String:String = b64encoder.flush();
http.SendSnapshot(b64String);
}
catch (error:*)
{
Alert.show(error.message);
}
}

// А это нужно вот для чего. С сервера в качестве ответа на посланный снимок экрана
// возвращаются несколько последних сохраненных снимков. Их нужно превратить
// из byteArray в bitmap и последовательно добавить в компонент mylist.
// Для этой цели и служит loader. А так как он работает асинхронно, то заводим счетчик -
// чтобы хранить, какое по порядку изображение этим loader-ом обрабатывается.
private var loader : Loader = new Loader();
private var loaderCounter:int = 0;

// эта функция вызывается после получения и обработки ответа от сервера и запускает процесс
// добавления присланных изображений в mylist
public function refreshImages():void
{
mylist.dataProvider.removeAll();
loaderCounter = http.Images.length-1;
if (0 <= loaderCounter)
loader.loadBytes(http.Images[loaderCounter]);
}

// обработчик события COMPLETE для loader-а. Его вызов происходит,
// когда loader получил все данные и готов выдать bitmap
private function getBitmapData(e:Event):void
{
try
{
var loader:Loader = Loader(e.target.loader);
var bitmap:Bitmap = Bitmap(loader.content);
mylist.dataProvider.addItem(bitmap);

loaderCounter--;
if (0 <= loaderCounter)
loader.loadBytes(http.Images[loaderCounter]);
}
catch (error:*)
{
Alert.show(error.message);
}
}

// Функция, вызываемая по завершении загрузки проекта.
// Присваиваем loader-у обработчик и подключаемся к веб-камере
private function init():void
{
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, getBitmapData);
videoDisplay_creationComplete();
}

]]>
</fx:Script>

</s:Application>


3. Файл HTTPServiceEngine.as - что зря пропадать снапшотам, пусть складируются на сервере 8-) - класс для приема-передачи изображений через AJAX.
package
{
import mx.rpc.http.HTTPService;
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;

import mx.core.FlexGlobals;
import mx.controls.Alert;

import mx.utils.Base64Decoder;

public class HTTPServiceEngine
{
private static var HTTPServiceName :String = "http://myserver/ajax.py";

private var httpService :HTTPService = null;

// буфер для хранения изображений, присланных с сервера, в виде байтовых массивов
public var Images :Array = new Array();

// конструктор
public function HTTPServiceEngine()
{
httpService = new HTTPService();
httpService.url = HTTPServiceEngine.HTTPServiceName;
httpService.method = "POST";
httpService.addEventListener("result", HTTPResult);
httpService.addEventListener("fault", HTTPFault);
}


// отправляем на сервер снимок и ожидаем в ответ список последних изображений
public function SendSnapshot(v:String):void
{
var parameters:Object = {};
parameters["r"] = "snapshot";
parameters["s"] = v;
httpService.send(parameters);
}


// сервер должен вернуть список последних изображений в таком формате:
// [file]
// изображение 1, закодированное в base64
// [file]
// изображение 2, закодированное в base64
// и т.п.
private function HTTPResult(event:ResultEvent):void
{
var result:String = String(event.result);
var buffer:String = "";

// готовим декодер
var b64decoder:Base64Decoder = new Base64Decoder();

// очищаем буфер для хранения полученных с сервера снимков
Images.length = 0;

// парсим ответ
var lines:Array = result.split("\n");
for (var i:int = 0; i < lines.length; i++)
{
if (lines[i] == "[file]")
{
if (buffer != "")
{
b64decoder.decode(buffer);
Images.push(b64decoder.toByteArray());
}
buffer = "";
}
else
buffer += lines[i];
}
if (buffer != "")
{
b64decoder.decode(buffer);
Images.push(b64decoder.toByteArray());
}

// отображаем полученные изображения на экране
FlexGlobals.topLevelApplication.refreshImages();
}

// обработчик ошибки взаимодействия с сервером
public function HTTPFault(event:FaultEvent):void
{
var faultstring:String = event.fault.faultString;
Alert.show(faultstring);
}
}
}


Ну и, наконец, собственно результат:



Комментариев нет:

Отправить комментарий