Пример работы с getUserMedia и <canvas> в Zen Framework по мотивам «HTML5 Exploding Camera Demo»

Начиная с версии 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>.

Код тестировался на:

  1. Caché 2012.2 Release Candidate (Unicode, x64);
  2. Opera 12.00 Release;
  3. Chrome 21 beta.

Примечание: WebRTC можно включить в браузере Chrome 18.0.1008 и его более поздних версиях на странице about:flags или chrome://flags

Код класса ZEN-страницы:
Class demo.camcanvas Extends %ZEN.Component.page
{

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 "http://www.intersystems.com/zen" ]
{
<
page xmlns="http://www.intersystems.com/zen" title="HTML5 Демо &quot;Взрывающаяся Камера&quot;" id="page">
  <
timer
    
id="timer"
    
timeout="0"
    
ontimeout="zenPage.processFrame();"
  
/>
  <
html
    
id="main"
    
align="center"
    
hidden="true">
    <
video
      
id="sourcevid"
      
autoplay="autoplay"
      
hidden="true"
    
>Извините, Ваш браузер не поддерживает тег &lt;video&gt;. Пожалуйста, попробуйте <a href="http://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 
copycanvas.width / 16;
      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, 00);
  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.originXtile.currentX)*0.2;
      
var diffy (tile.originYtile.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);
}

ClientMethod zindexSort(
  
a,
  
b) [ Language = javascript ]
{
  
return (a.forceb.force);
}

/// Получить координаты нажатия/мыши для взрыва полотна
ClientMethod 
dropBomb(
  
event,
  
obj) [ Language = javascript ]
{
  event.preventDefault();
  
var posx = 0;
  
var posy = 0;
  
var 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 posxobj.offsetLeft;
  
var canvasY posyobj.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.currentXx;
    
var ydiff tile.currentYy;
    
var dist Math.sqrt(xdiff*xdiff ydiff*ydiff);
    
var rnd Math.random();

    var randRange = 180+(rnd*10);
    
var range randRangedist;
    
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.5rnd;
    }
  }
  tiles.sort(zindexSort);
  
this.processFrame();
}

ClientMethod createTiles() [ Language = javascript ]
{
  
var offsetX (TILE_CENTER_WIDTH+(PAINTWIDTHSOURCERECT.width)/2 >> 0);
  
var offsetY (TILE_CENTER_HEIGHT+(PAINTHEIGHTSOURCERECT.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.

Примечание: все вышеперечисленные инструменты поддерживают русский интерфейс, но далее при описании пунктов меню будет использоваться их английский вариант.

Итак:

  1. откройте Caché Studio;
  2. выберите область “USER”: File–>Change Namespace или (F4);
  3. запустите мастер импорта файла: Tools–>Import Local или (Ctrl+I);
  4. выберите файл “sources.xml”;
  5. установите галочку Compile Imported Items и нажмите OK;
  6. откройте исходный код нашего класса demo.camcanvas из дерева классов;


    увеличить

  7. откройте веб-страницу: View->Web Page или (F5).

По умолчанию ссылка на нашу страницу будет иметь следующий вид:

http://localhost:xxxx/csp/user/demo.camcanvas.cls

, где xxxx – это номер порта, который мы указали при инсталляции СУБД Caché, на котором будет работать встроенный веб-сервер Apache.

PS: класс demo.camcanvas не составит труда переделать под технологию CSP (Caché Server Pages).

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *