Начиная с версии 2012.1, в СУБД Caché появился встроенный ZEN-компонент <canvas>.
Примечание: скачать однопользовательскую версию СУБД Caché можно отсюда.
А в последнем релизе Opera 12 появилась встроенная поддержка функции getUserMedia (WebRTC 1.0: Real-time Communication Between Browsers), которая даёт возможность обращаться к устройствам, генерирующим медиапоток, например к веб-камере.
Примечание: Сводная таблица поддержки getUserMedia/Stream API в настольных и мобильных браузерах.
Для демонстрации обеих этих возможностей, используя встроенный в СУБД Caché фреймворк ZEN, за основу было выбрано демо: HTML5 Exploding Camera Demo.
Примечание: Оригинальное демо в онлайне.
Также в код были дополнительно добавлены четыре фильтра на выводимое видео с веб-камеры для демонстрации работы с отдельными пикселями компонента <canvas>.
Код тестировался на:
- Caché 2012.2 Release Candidate (Unicode, x64);
- Opera 12.00 Release;
- Chrome 21 beta.
Примечание: WebRTC можно включить в браузере Chrome 18.0.1008 и его более поздних версиях на странице about:flags или chrome://flags
{ Parameter JSINCLUDES As STRING = "zenCSLM.js"; /// Этот блок Style содержит определение CSS-стиля страницы.
XData Style
{
<style type="text/css">
* {
margin:0;
padding:0;
}
a {
color:#99f;
}
#page {
background:#000;
color:#fff;
font-family:'Helvetica Neue', 'Free Sans', 'Deja Vu Sans', Arial, Helvetica, sans-serif;
margin:0px;
padding:0px;
}
#main {
color:#fff;
border:solid 2px #c00;
border-radius:1em;
line-height:1.5;
margin:2em auto;
padding:1em;
width:50%;
}
#rs {
background:#3af;
color:#777;
}
</style>
} XData Contents [ XMLNamespace = "www.intersystems.com/zen" ]
{
<page xmlns="www.intersystems.com/zen" title="HTML5 Демо " Взрывающаяся Камера"" id="page">
<timer
id="timer"
timeout="0"
ontimeout="zenPage.processFrame();"
/>
<html
id="main"
align="center"
hidden="true">
<video
id="sourcevid"
autoplay="autoplay"
hidden="true"
>Извините, Ваш браузер не поддерживает тег <video>. Пожалуйста, попробуйте <a href="ru.opera.com/download/">Opera</a>.</video>
</html>
<radioSet
id="rs"
align="center"
hidden="true"
displayList="Обычный, Рельефный, Красный, Инверсный, Монохромный"
valueList="filterNormal,filterEmboss,filterRed,filterInversed,filterGrayscale"
value="filterNormal"
onchange="zenPage.changeFilter(zenThis.getValue());"
/>
<canvas id="output" hidden="true"/>
<canvas id="sourcecopy" hidden="true"/>
</page>
} ClientMethod changeFilter(strFunction) [ Language = javascript ]
{
eval("filter = 'this." + strFunction + "();'");
} ClientMethod filterNormal() [ Language = javascript ]
{
} ClientMethod filterInversed() [ Language = javascript ]
{
var imageData = copy.getImageData(0,0,copycanvas.width,copycanvas.height);
var data=imageData.data;
var media = (data[0]+data[1]+data[2])/3>>0;
data[
0]=media;data[1]=media;
data[2]=media;
for(var i=4,len=data.length;i<len;i+=4) {
media = 255-(data[i]+data[i+1]+data[i+2])/3>>0;
data[i]
=media;data[i+1]=media;
data[i+2]=media;
}
copy.putImageData(imageData,0,0);
} ClientMethod filterGrayscale() [ Language = javascript ]
{
var imageData = copy.getImageData(0,0,copycanvas.width,copycanvas.height);
var data=imageData.data;
for(var i=0,len=data.length;i<len;i+=4) {
var media = (data[i]+data[i+1]+data[i+2])/3>>0;
data[i]
=media;data[i+1]=media;
data[i+2]=media;
}
copy.putImageData(imageData,0,0);
} ClientMethod filterRed() [ Language = javascript ]
{
var imageData = copy.getImageData(0,0,copycanvas.width,copycanvas.height);
var data=imageData.data;
for(var i=0,len=data.length;i<len;i+=4) {
data[i+1]=0;
data[i+2]=0;
}
copy.putImageData(imageData,0,0);
} ClientMethod filterEmboss() [ Language = javascript ]
{
var imageData = copy.getImageData(0,0,copycanvas.width,copycanvas.height);
var data=imageData.data;
var media = (data[0]+data[1]+data[2])/3>>0;
data[0]=media;
data[1]=media;
data[2]=media;
media
= (data[4]+data[5]+data[6])/3>>0;data[4]=media;
data[5]=media;
data[6]=media;
for(var i=8,len=data.length;i<len;i+=4) {
media = (data[i]+data[i+1]+data[i+2])/3>>0;
data[i]
=media;data[i+1]=media;
data[i+2]=media;
data[i
-8]=(data[i-8]+255-media)/2>>0;data[i-7]=(data[i-7]+255-media)/2>>0;
data[i-6]=(data[i-6]+255-media)/2>>0;
}
copy.putImageData(imageData,0,0);
} ClientMethod processFrame() [ Language = javascript ]
{
if(!isNaN(video.duration)){
if(SOURCERECT.width == 0){
SOURCERECT = {x:0,y:0,width:video.videoWidth,height:video.videoHeight};
copycanvas.width = video.videoWidth;
copycanvas.height = video.videoHeight;
TILE_WIDTH
TILE_HEIGHT = copycanvas.height / 16;
TILE_CENTER_WIDTH = TILE_WIDTH / 2 >> 0;
TILE_CENTER_HEIGHT = TILE_HEIGHT / 2 >> 0;
this.createTiles();
zenSetProp('output','hidden',false);
zenSetProp('rs','hidden',false);
}
}
//копирование плитки
copy.drawImage(video, 0, 0);
eval(filter);
draw.clearRect(PAINTX, PAINTY,PAINTWIDTH,PAINTHEIGHT);
for(var i=0, len = tiles.length; i<len; i++){
var tile = tiles[i];
if(tile.force > 0.0001){
//расширение
var force = tile.force;
tile.moveX *= force;
tile.moveY *= force;
tile.moveRotation *= force;
tile.currentX += tile.moveX;
tile.currentY += tile.moveY;
tile.rotation += tile.moveRotation;
tile.rotation %= 360;
tile.force *= 0.9;
if(tile.currentX <= 0 || tile.currentX >= PAINTWIDTH){
tile.moveX *= -1;
}
if(tile.currentY <= 0 || tile.currentY >= PAINTHEIGHT){
tile.moveY *= -1;
}
}else if(tile.rotation != 0 || tile.currentX != tile.originX || tile.currentY != tile.originY){
//схлопывание
var diffx = (tile.originX-tile.currentX)*0.2;
var diffy = (tile.originY-tile.currentY)*0.2;
var diffRot = (0-tile.rotation)*0.2;
if(this.absolute(diffx) < 0.5){
tile.currentX = tile.originX;
}else{
tile.currentX += diffx;
}
if(this.absolute(diffy) < 0.5){
tile.currentY = tile.originY;
}else{
tile.currentY += diffy;
}
if(this.absolute(diffRot) < 0.5){
tile.rotation = 0;
}else{
tile.rotation += diffRot;
}
}else{
tile.force = 0;
}
draw.save();
draw.translate(tile.currentX, tile.currentY);
draw.rotate(tile.rotation*RAD);
draw.drawImage(copycanvas, tile.videoX, tile.videoY, TILE_WIDTH, TILE_HEIGHT, -TILE_CENTER_WIDTH, -TILE_CENTER_HEIGHT, TILE_WIDTH, TILE_HEIGHT);
draw.restore();
}
zen('timer').startTimer();
} ClientMethod successCallback(stream) [ Language = javascript ]
{
// Замена источника видеоэлемента потоком с камеры
video.src = window.URL.createObjectURL(stream) || stream;
video.play();
} ClientMethod errorCallback(error) [ Language = javascript ]
{
} /// Конструктор для отдельных плиток
ClientMethod Tile() [ Language = javascript ]
{
this.originX = 0;
this.originY = 0;
this.currentX = 0;
this.currentY = 0;
this.rotation = 0;
this.force = 0;
this.z = 0;
this.moveX= 0;
this.moveY= 0;
this.moveRotation = 0;
this.videoX = 0;
this.videoY = 0;
} /// Быстрее, чем Math.abs
ClientMethod absolute(x) [ Language = javascript ]
{
return (x < 0 ? -x : x);
} ClientMethod zindexSort(
a,
b) [ Language = javascript ]
{
return (a.force-b.force);
} /// Получить координаты нажатия/мыши для взрыва полотна
ClientMethod dropBomb(
event,
obj) [ Language = javascript ]
{
event.preventDefault();
var posx = 0;
var posy = 0;
var e = event || window.event;
if (e.touches) {
posx = event.touches[0].pageX;
posy = event.touches[0].pageY;
} else if (e.pageX || e.pageY) {
posx = e.pageX;
posy = e.pageY;
} else if (e.clientX || e.clientY) {
posx = e.clientX + ZLM.getPageXOffset() + document.documentElement.scrollLeft;
posy = e.clientY + ZLM.getPageYOffset() + document.documentElement.scrollTop;
}
var canvasX = posx-obj.offsetLeft;
var canvasY = posy-obj.offsetTop;
this.explode(canvasX, canvasY);
} ClientMethod explode(
x,
y) [ Language = javascript ]
{
for(var i=0, len = tiles.length; i<len; i++){
var tile = tiles[i];
var xdiff = tile.currentX-x;
var ydiff = tile.currentY-y;
var dist = Math.sqrt(xdiff*xdiff + ydiff*ydiff);
var rnd = Math.random();
var randRange = 180+(rnd*10);
var range = randRange-dist;
var force = 3*(range/randRange);
if(force > tile.force){
tile.force = force;
var radians = Math.atan2(ydiff, xdiff);
tile.moveX = Math.cos(radians);
tile.moveY = Math.sin(radians);
tile.moveRotation = 0.5-rnd;
}
}
tiles.sort(zindexSort);
this.processFrame();
} ClientMethod createTiles() [ Language = javascript ]
{
var offsetX = (TILE_CENTER_WIDTH+(PAINTWIDTH-SOURCERECT.width)/2 >> 0);
var offsetY = (TILE_CENTER_HEIGHT+(PAINTHEIGHT-SOURCERECT.height)/2 >> 0);
var y=0;
while(y < SOURCERECT.height){
var x=0;
while(x < SOURCERECT.width){
var tile = new this.Tile();
tile.videoX = x;
tile.videoY = y;
tile.originX = offsetX+x;
tile.originY = offsetY+y;
tile.currentX = tile.originX;
tile.currentY = tile.originY;
tiles.push(tile);
x+=TILE_WIDTH;
}
y+=TILE_HEIGHT;
}
} ClientMethod onloadHandler() [ Language = javascript ]
{
TILE_WIDTH = 32;
TILE_HEIGHT = 24;
TILE_CENTER_WIDTH = TILE_WIDTH / 2;
TILE_CENTER_HEIGHT = TILE_HEIGHT / 2;
SOURCERECT = {x:0, y:0, width:0, height:0};
PAINTX = 0;
PAINTY = 0;
PAINTWIDTH = ZLM.getViewportWidth();
PAINTHEIGHT = ZLM.getViewportHeight();
RAD = Math.PI/180;
tiles
= [];filter = 'this.filterNormal();';
video = document.getElementById('sourcevid');
copycanvas = zen('sourcecopy').findElement('canvas');
copy = zen('sourcecopy').getContext();
var outputcanvas = zen('output').findElement('canvas');
draw = zen('output').getContext();
outputcanvas.width = PAINTWIDTH;
outputcanvas.height = PAINTHEIGHT-20;
var mouse_down = ('createTouch' in document ? 'ontouchstart' : 'onmousedown');
outputcanvas[mouse_down] = function(event) {
zenPage.dropBomb(event, this);
};
// Получить поток с камеры, используя функцию getUserMedia
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
if (navigator.getUserMedia) {
// This beautiful hack for the options is from @kanasansoft:
// http://www.kanasansoft.com/weblab/2012/06/arguments_of_getusermedia.html
var gumOptions = {video: true, toString: function(){return 'video';}};
navigator.getUserMedia(gumOptions, this.successCallback, this.errorCallback);
zen(
'timer').setProperty('timeout',33);zen('timer').startTimer();
} else {
zenSetProp('main','content','Ой, кажется, Ваш браузер не поддерживает функцию getUserMedia.<br>Пожалуйста, попробуйте <a href="http://ru.opera.com/download/">браузер, который имеет такую поддержку</a>.');
zenSetProp('main','hidden',false);
}
}
}
Исходники класса demo.camcanvas.
Импорт исходного кода, его компиляция и запуск примера
Импорт исходного кода (проекта, классов, данных и др.) можно осуществить с помощью:
Все эти инструменты доступны из меню Caché Launcher.
Для удобства запуска примера воспользуемся Caché Studio.
Примечание: все вышеперечисленные инструменты поддерживают русский интерфейс, но далее при описании пунктов меню будет использоваться их английский вариант.
Итак:
- откройте Caché Studio;
- выберите область «USER»: File–>Change Namespace или (F4);
- запустите мастер импорта файла: Tools–>Import Local или (Ctrl+I);
- выберите файл «sources.xml»;
- установите галочку Compile Imported Items и нажмите OK;
- откройте исходный код нашего класса demo.camcanvas из дерева классов;
- откройте веб-страницу: View->Web Page или (F5).
По умолчанию ссылка на нашу страницу будет иметь следующий вид:
http://localhost:xxxx/csp/user/demo.camcanvas.cls
, где xxxx — это номер порта, который мы указали при инсталляции СУБД Caché, на котором будет работать встроенный веб-сервер Apache.
PS: класс demo.camcanvas не составит труда переделать под технологию CSP (Caché Server Pages).
Автор: servitRM