
Starting with version 2012.1, the embedded
canvas <canvas> ZEN component has appeared in Caché DBMS.
Note: you can download the free single-user version of the Caché DBMS here .
And in the latest release of
Opera 12 , built-in support for the
getUserMedia function (
WebRTC 1.0 : Real-time Communication Between Browsers) appeared, which makes it possible to access devices that generate a media stream, such as a webcam.
Note: Support table of getUserMedia / Stream API in desktop and mobile browsers.
To demonstrate both of these features, using the
ZEN framework built into the Caché DBMS, the demo was chosen as the basis:
HTML5 Exploding Camera Demo .
Note: The original demo is online .
Also, four filters were added to the code to the output video from the webcam to demonstrate how to work with individual pixels of the
<canvas> component.
The code was tested on:
')
- Caché 2012.2 Release Candidate (Unicode, x64);
- Opera 12.00 Release;
- Chrome 21 beta.
Note: WebRTC can be enabled in Chrome 18.0.1008 and later versions on the about: flags or chrome: // flags page

ZEN page class codeClass demo.camcanvas Extends% ZEN.Component.page
{
Parameter JSINCLUDES As STRING = "zenCSLM.js" ;
/// This Style block defines the CSS style for the page.
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 Demo & quot; Exploding Camera & 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"
> Sorry, your browser does not support the & lt; video & gt; . Please try <a href = "ru.opera.com/download/"> Opera </ a >. </ Video >
</ html >
< radioSet
id = "rs"
align = "center"
hidden = "true"
displayList = "Normal, Embossed, Red, Inverse, Monochrome"
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 tiles
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 ) {
//expansion
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) {
// collapse
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]
{
// Replacing the source of the video element with camera stream
video.src = window.URL.createObjectURL (stream) || stream;
video.play ();
}
ClientMethod errorCallback ( error ) [ Language = javascript]
{
}
/// Constructor for individual tiles
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 ;
}
/// Faster than Math.abs
ClientMethod absolute ( x ) [ Language = javascript]
{
return (x <0? - x : x);
}
ClientMethod zindexSort (
a ,
b ) [ Language = javascript]
{
return (a.force - b.force);
}
/// Get the coordinates of the click / mouse for the explosion of the canvas
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 );
};
// Get the stream from the camera using the getUserMedia function
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
if (navigator.getUserMedia) {
// This is a 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' , 'Oh, it seems your browser does not support the getUserMedia function. <br> Please try <a href="http://rupera.ru/download/"> a browser that has such support </a>. ' );
zenSetProp ( 'main' , 'hidden' , false);
}
}
} Sources class
demo.camcanvas .
Import source code, compile it and run the example
Importing source code (project, classes, data, etc.) can be done using:
All of these tools are available from the
Caché Launcher menu.
For the convenience of running the example, use
Caché Studio .
Note: all the above tools support the Russian interface, but later in the description of the menu items their English version will be used.
So:
- open Caché Studio ;
- select the “USER” area: File–> Change Namespace or (F4);
- run the file import wizard: Tools–> Import Local or (Ctrl + I);
- select the file “sources.xml”;
- check the Compile Imported Items box and click OK ;
- open the source code of our demo.camcanvas class from the class tree;

to increase
- Open the webpage: View-> Web Page or (F5).
By default, the link to our page will look like this:
http: // localhost: xxxx / csp / user / demo.camcanvas.cls
where
xxxx is the port number that we specified when installing the Caché DBMS, which will run the
Apache embedded web server .
PS:
demo.camcanvas class
will not be difficult to remake under
CSP technology (Caché Server Pages).