2. Abstract.
CoW is a Web (mobile) Aplication that allows the colaborative editing of a
whiteboard using the canvas element provided by HTML5. The aplication allows users to
use different „pencils”, insert text, geometric figures, external images and draw upun the
whiteboard. The whiteboard can also be exported as a PNG image for the purpose of being
saved and later restored.
1. Introduction
CoW was created during the Client Side Web Application Development laboratory
held by Negru Stefan and having as course teacher Dr Sabin-Corneliu Buraga.
2. Tehnologies used
CoW was created using JavaScript, HTML5, and Socket.io tehnologies. The main
element of the CoW application is a canvas (HTML5 element) which allows the running of
drawing scripts. The functions that allow the user to interact with the app are part of the
jQuery lirbrary(for PC users) and the jQuery mobile library(for mobile users).
Comunicating between users is done by means of the Socket.IO module of the Node.JS
project, which, amongst other functionalities provides real time comunication amongst
multiple clients.
2.1 JavaScript+Canvas
2.1.1 Drawing functions
Each canvas has a drawing contex. In the CoW app, the context is a 2D one and is
called as follows:
var canvas = document.getElementById("sheet");
var context = canvas.getContext("2d");
Where sheet is the canvas’s ID within the index.html file.
For the mobile website the canvas is automaticly generated, resized and placed
within the contentholder div as follows:
function resizeCanvas(){
var w = window.innerWidth / 1.2;
var h = window.innerHeight / 1.2;
var canvasString = '<canvas id="mainCanvas" width="'
+ w + '" height="' + h + '">Canvas is not
supported</canvas>';
$('#contentholder').empty();
$(canvasString).appendTo('#contentholder');
context = $('#mainCanvas').get(0).getContext('2d');}
To draw within the canvas’s context we use the following functions:
To draw freely with the pencil:
3. var line = function(x1, y1, x2, y2, s) {
s.globalCompositeOperation = 'source-over';
setStyle(s);
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.closePath();
context.stroke();
}
Where x1 and y1 are the coordonates where the mouseDown (touchstart for
mobile)event took place and x2 and y2 are the coordinates where mouseMove (touchmove
for mobile) event took place.
To draw a line:
var line = function(x1, y1, x2, y2, s) {
s.globalCompositeOperation = 'source-over';
setStyle(s);
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.closePath();
context.stroke();
}
Where x1 and y1 are the coordonates where the mouseDown (touchstart for
mobile)event took place and x2 and y2 are the coordinates where the mouseUp (touchend
for mobile) event took place.
To draw a circle:
var circle = function(x1, y1, x2, y2, s) {
s.globalCompositeOperation = 'source-over';
minX = (x1<x2) ? x1 : x2;
minY = (y1<y2) ? y1 : y2;
radiusX = Math.abs(x1 - x2) / 2;
radiusY = Math.abs(y1 - y2) / 2;
context.beginPath();
setStyle(s);
context.arc(minX+radiusX, minY+radiusY, radiusX,
0, 2*Math.PI, false);
context.closePath();
context.stroke();
}
Where x1 and y1 are the coordonates where the mouseDown (touchstart for
mobile)event took place and x2 and y2 are the coordinates where the mouseUp (touchend
for mobile) event took place.
To draw a rectangle:
4. var rectangle = function(x1, y1, x2, y2, s) {
s.globalCompositeOperation = 'source-over';
minX = (x1<x2) ? x1 : x2;
minY = (y1<y2) ? y1 : y2;
xl = Math.abs(x1-x2);
yl = Math.abs(y1-y2);
setStyle(s);
context.beginPath();
context.rect(minX, minY, xl, yl);
context.closePath();
context.stroke();
}
Where x1 and y1 are the coordonates where the mouseDown (touchstart for
mobile)event took place and x2 and y2 are the coordinates where the mouseUp (touchend
for mobile) event took place.
Withing all four functions s represents the shape’s style (colour, thickness,
transparency, lineJoin and lineCap).
To erase:
var eraser = function(x1, y1, x2, y2, s) {
s.globalCompositeOperation = 'copy';
s.strokeStyle = 'rgba(0,0,0,0)';
setStyle(s);
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.closePath();
context.stroke();
}
Where x1 and y1 are the coordonates where the mouseDown (touchstart for
mobile)event took place and x2 and y2 are the coordinates where mouseMove (touchmove
for mobile) event took place.
For a new sheet we use the following function:
$('#newSheet').click(function(){
canvas.width = canvas.width;
});
2.1.2 Mouse/Touch event handlers
5. The following functions are used to handle mouse events (mouseDown, mouseUp,
mouseMove):
// event handler for Mouse Down
$('#sheet').mousedown(function(e) {
mouseDown = true; //used for checking if left click is down
mouseX = e.pageX - this.offsetLeft; //used to get the
position of the mouse inside the canvas
mouseY = e.pageY - this.offsetTop; //used to get the position
of the mouse inside the canvas
context.moveTo(mouseX, mouseY);
switch(tool){
case Pen: // draws in case the pen is selected
pencil(mouseX, mouseY, mouseX, mouseY, style);
message = { "x1":mouseX, "y1":mouseY, "x2":mouseX+1,
"y2":mouseY, "tool":Pen, "style":style };
socket.send(JSON.stringify(message));
break;
case Text: // text insertion
if($('#textVal').val() != '')
{ //checks for the bold, italic and underline options
font = '';
if($('#bold').is(':checked'))
font += 'bold ';
if($('#underline').is(':checked'))
font += 'underline ';
if($('#italic').is(':checked'))
font += 'italic ';
font += $('#penSize').slider('value')*5 + 'px sans-serif';
context.font = font;
context.fillStyle = style.strokeStyle;
context.fillText($('#textVal').val(), mouseX, mouseY);
message = { "x":mouseX, "y":mouseY, "tool":Text, "
font":font, "text":$('#textVal').val(), "style":style
};
socket.send(JSON.stringify(message)); }
break;
case IMAGE: // drawing an image onto the canvas at the
mouseX mouseY position
context.drawImage(image, mouseX, mouseY);
message = { "x":mouseX, "y":mouseY, "image":image.src,
"tool":IMAGE };
socket.send(JSON.stringify(message));
break;
break; }
6. });
// event handler for Mouse Up
$('#sheet').mouseup(function(e) {
mouseDown = false; // click is no longer pressed
newX = e.pageX - this.offsetLeft;// used to get the position
of the mouse inside the canvas
newY = e.pageY - this.offsetTop;// used to get the position
of the mouse inside the canvas
oldX = mouseX;
oldY = mouseY;
switch(tool) {
case Pen: // do nothing because the drawing has been done
insed the mousemove function
break;
case Circ: //draws the circle from the oldX and oldY
position to the newX newY position with the
diameter give by the distance between the 2
circle(oldX, oldY, newX, newY, style);
message = { "x1":oldX, "y1":oldY, "x2":newX, "y2":newY,
"tool":Circ, "style":style };
socket.send(JSON.stringify(message));
break;
case Rec:// draws the rectangle from the oldX and oldY
position to the newX newY position with the
diameter give by the distance between the 2
rectangle(oldX, oldY, newX, newY, style);
message = { "x1":oldX, "y1":oldY, "x2":newX, "y2":newY,
"tool":Rec, "style":style };
socket.send(JSON.stringify(message));
break;
case Line://draws the line from the oldX and oldY position
in the newX and newY position
line(oldX, oldY, newX, newY, style);
message = { "x1":oldX, "y1":oldY, "x2":newX, "y2":newY,
"tool":Line, "style":style };
socket.send(JSON.stringify(message));
break; }
});
// event handler for mouse move
$('#sheet').mousemove(function(e) {
newX = e.pageX - this.offsetLeft;
newY = e.pageY - this.offsetTop;
7. if(mouseDown) { //as long as the mouse is downdraws or erases
depending on the selected tool
oldX = mouseX;
oldY = mouseY;
switch(tool){
case Pen:
pencil(oldX, oldY, newX, newY, style);
message = { "x1":oldX, "y1":oldY, "x2":newX,
"y2":newY,"tool":Pen,"style":style };
socket.send(JSON.stringify(message));
mouseX = newX;
mouseY = newY;
break;
case Eraser:
eraser(oldX, oldY, newX, newY, style);
message = { "x1":oldX, "y1":oldY, "x2":newX, "y2":newY,
"tool":Eraser, "style":style };
socket.send(JSON.stringify(message));
mouseX = newX;
mouseY = newY;
break;
break; } }
});
// handles the mouse leaving the canvas
$('#sheet').mouseleave(function() {
mouseDown = false;
});
The handling of the touch events is done pretty much the same, with the exception
of the syntax upon binding the events to the canvas:
//the equivalent of mouseDown
$('#mainCanvas').bind('touchstart',(function(e) { }
//the equivalent of mouseUp
$('#mainCanvas').bind('touchend',(function(e) { }
//the equivalent of mousemove
$('#mainCanvas').bind('touchmove',(function(e) { }
These events appear upon mouse/touch actions on the browser surface.
8. 2.1.3 Choosing the color
Choosing the color is done by means of
the 3 sliders you can see on your right which
indicate the percentage of red green and blue the
color will have. The sample of the output color is
located in the #selected color div.
// color and size
//adjsusts the brush depending
on the information provided by
the sliders (amount of Red,
Green and Blue)
$('#red').slider({
value: 95,
min: 0,
max: 255,
slide: function(event, ui) {
changeColor();}
}).width(160);
$('#green').slider({
value: 221,
min: 0,
max: 255,
slide: function(event, ui) {
changeColor();}
}).width(160);
$('#blue').slider({
value: 122,
min: 0,
max: 255,
slide: function(event, ui) {changeColor();}
}).width(160);
$('#opac').slider({
value: 100,
min: 0,
max: 100,
slide: function(event, ui) {changeColor();}
9. }).width(160);
//Changes the brush colour update'ing the style and the
colour of the Selected colour div's background
var changeColor = function(){
c='rgba('+$('#red').slider('value')+','+$('#green').slider('v
alue')+','+$('#blue').slider('value')+','+$('#opac').slider('
value') / 100) + ')';
$('#selectedColor').css({backgroundColor: c});
style.strokeStyle = 'rgb(' + $('#red').slider('value') + ','
+$('#green').slider('value')+','+$('#blue').slider('val
ue') + ')';
style.globalAlpha = ($('#opac').slider('value') / 100);
}
2.2 Communicating between users
Data transmition is done using Socket.IO which automaticly selects the data
transmition protocol for each browser that calls it. Installing NodeJS and Socket.IO is
detailed in the youtube link of the biblografy.
Server side:
var io = require('socket.io').listen(31520);
io.sockets.on('connection', function(client){
client.on('message',function(event){
client.broadcast.send(event);
});
client.on('disconnect',function(){
});
});
Client side:
//connects to the server
var socket = io.connect('http://localhost:31520');
//sends the tool data to the other clients
socket.on('message', function(Data) {
Data = $.parseJSON(Data);
switch(Data.tool){
case Pen:
pencil(Data.x1, Data.y1, Data.x2, Data.y2, Data.style);
break;
case Circ:
circle(Data.x1, Data.y1, Data.x2, Data.y2, Data.style);
break;
case Rec:
rectangle(Data.x1, Data.y1, Data.x2, Data.y2,
10. Data.style);
break;
case Line:
line(Data.x1, Data.y1, Data.x2, Data.y2, Data.style);
break;
case Text:
context.font = Data.font;
context.fillStyle = Data.style.strokeStyle;
context.fillText(Data.text, Data.x, Data.y);
break;
case New:
canvas.width = canvas.width;
break;
case IMAGE:
image = new Image();
image.src = Data.image;
context.drawImage(image, Data.x, Data.y);
break;
case Eraser:
eraser(Data.x1, Data.y1, Data.x2, Data.y2, Data.style);
break;
break;}
});
2.3 Usage
2.3.1 For the desktop app
11. The tools are displayed in the upper left corner. The drawing possibilities are:
pencil, line, rectangle, circle and eraser, which are to be used upon the canvas(the
whiteboard). Bellow those we have the option to export, which opens the content of the
canvas as a new PNG image in a new tab, and the browse button which opens a file browser
that can select an image to be inserted onto the canvas upon click(these 2 can be combined
and used as a sort of save/load function.
On the right side the brush options are displayed. They can be used to adjust the
size, color and opacity of the brush. Above them we have the New Sheet button which
clears the canvas.
Bellow the brush options we have the text insertion options. The text goes in the
textbox and will be inserted upon click after first selecting Insert Text.
2.3.2 For the mobile app
12. From the upper side of the screen we may select the tool we wish to use and it
shall be used by touching the screen.
Bibliografie:
http://socket.io
http://profs.info.uaic.ro/~busaco/teach/courses/cliw/web.html
http://www.w3schools.com/
http://docs.jquery.com/Main_Page
http://jquerymobile.com/demos/1.0/
http://www.youtube.com/watch?v=3nnpCXtPXHg