Пример работы с 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 "www.intersystems.com/zen" ]
    {
    <
    page xmlns="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="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.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);
    }

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

    Итак:

    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).
    InterSystems
    103.17
    InterSystems IRIS: СУБД, ESB, BI, Healthcare
    Share post

    Comments 0

    Only users with full accounts can post comments. Log in, please.