JavaScript DHTML/Ajax Layer/Graph — различия между версиями
Admin (обсуждение | вклад) м (1 версия) |
|
(нет различий)
|
Текущая версия на 07:15, 26 мая 2010
Содержание
Drag and drop graph
// http://js-graph-it.sf.net
// License: GNU Library or Lesser General Public License (LGPL)
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>js-graph.it homepage</title>
<script type="text/javascript">
/*********************
* browser detection *
*********************/
var ie=document.all;
var nn6=document.getElementById&&!document.all;
/*****************
* drag and drop *
*****************/
var isdrag=false;
var mouseStartX, mouseStartY; // mouse position when drag starts
var elementStartX, elementStartY; // element position when drag starts
var elementToMove;
var blockToMove;
// an array containing bounds to be respected while dragging elements,
// these bounds are left, top, left + width, top + height of the parent element.
var bounds = new Array(4);
function movemouse(e)
{
if (isdrag)
{
var currentMouseX = nn6 ? e.clientX : event.clientX;
var currentMouseY = nn6 ? e.clientY : event.clientY;
var newElementX = elementStartX + currentMouseX - mouseStartX;
var newElementY = elementStartY + currentMouseY - mouseStartY;
// check bounds
// note: the "-1" and "+1" is to avoid borders overlap
if(newElementX < bounds[0])
newElementX = bounds[0] + 1;
if(newElementX + elementToMove.offsetWidth > bounds[2])
newElementX = bounds[2] - elementToMove.offsetWidth - 1;
if(newElementY < bounds[1])
newElementY = bounds[1] + 1;
if(newElementY + elementToMove.offsetHeight > bounds[3])
newElementY = bounds[3] - elementToMove.offsetHeight - 1;
// move element
elementToMove.style.left = newElementX + "px";
elementToMove.style.top = newElementY + "px";
// elementToMove.style.left = newElementX / elementToMove.parentNode.offsetWidth * 100 + "%";
// elementToMove.style.top = newElementY / elementToMove.parentNode.offsetHeight * 100 + "%";
elementToMove.style.right = null;
elementToMove.style.bottom = null;
if(blockToMove)
blockToMove.onMove();
return false;
}
}
/**
* finds the innermost draggable element starting from the one that generated the event "e"
* (i.e.: the html element under mouse pointer), then setup the document"s onmousemove function to
* move the element around.
*/
function selectmouse(e)
{
var eventSource = nn6 ? e.target : event.srcElement;
while (eventSource != document.body && !hasClass(eventSource, "draggable"))
{
eventSource = nn6 ? eventSource.parentNode : eventSource.parentElement;
}
// if a draggable element was found, calculate its actual position
if (hasClass(eventSource, "draggable"))
{
isdrag = true;
elementToMove = eventSource;
// calculate start point
//elementStartX = calculateOffsetLeft(elementToMove);
//elementStartY = calculateOffsetTop(elementToMove);
elementStartX = elementToMove.offsetLeft;
elementStartY = elementToMove.offsetTop;
// calculate mouse start point
mouseStartX = nn6 ? e.clientX : event.clientX;
mouseStartY = nn6 ? e.clientY : event.clientY;
// calculate bounds as left, top, width, height of the parent element
if(elementToMove.parentNode.style.position == "absolute")
{
bounds[0] = 0;
bounds[1] = 0;
}
else
{
bounds[0] = calculateOffsetLeft(elementToMove.parentNode);
bounds[1] = calculateOffsetTop(elementToMove.parentNode);
}
bounds[2] = bounds[0] + elementToMove.parentNode.offsetWidth;
bounds[3] = bounds[1] + elementToMove.parentNode.offsetHeight;
// either find the block related to the dragging element to call its onMove method
blockToMove = findBlock(eventSource.id);
document.onmousemove = movemouse;
return false;
}
}
document.onmousedown=selectmouse;
document.onmouseup=new Function("isdrag=false");
/*************
* Constants *
*************/
var AUTO = 0;
var HORIZONTAL = 1;
var VERTICAL = 2;
/**************
* Inspectors *
**************/
var inspectors = new Array();
/**
* The canvas class.
* This class is built on a div html element.
*/
function Canvas(htmlElement)
{
/*
* initialization
*/
this.id = htmlElement.id;
this.htmlElement = htmlElement;
this.blocks = new Array();
this.connectors = new Array();
this.initCanvas = function()
{
// inspect canvas children to identify first level blocks
this.findNestedBlocksAndConnectors(this.htmlElement);
// init connectors
var i;
for(i = 0; i < this.connectors.length; i++)
{
this.connectors[i].initConnector();
}
}
this.findNestedBlocksAndConnectors = function(node)
{
var children = node.childNodes;
var i;
var offsetLeft = calculateOffsetLeft(this.htmlElement);
var offsetTop = calculateOffsetTop(this.htmlElement);
for(i = 0; i < children.length; i++)
{
// move element in a "correct relative" position and set it size as fixed
if(getStyle(children[i], "position") == "absolute")
{
children[i].style.left = children[i].offsetLeft + offsetLeft + "px";
children[i].style.top = children[i].offsetTop + offsetTop + "px";
children[i].style.width = children[i].offsetWidth;
children[i].style.height = children[i].offsetHeight;
}
if(isBlock(children[i]))
{
// block found initialize it
var newBlock = new Block(children[i], this);
newBlock.initBlock();
this.blocks.push(newBlock);
}
else if(isConnector(children[i]))
{
// connector found, just create it, source or destination blocks may not
// have been initialized yet
var newConnector = new Connector(children[i], this);
this.connectors.push(newConnector);
}
else
{
// continue searching nested elements
this.findNestedBlocksAndConnectors(children[i]);
}
}
}
/*
* methods
*/
this.print = function()
{
var output = "<ul><legend>canvas: " + this.id + "</legend>";
var i;
for(i = 0; i < this.blocks.length; i++)
{
output += "<li>";
output += this.blocks[i].print();
output += "</li>";
}
output += "</ul>";
return output;
}
/*
* This function searches for a nested block with a given id
*/
this.findBlock = function(blockId)
{
var result;
var i;
for(i = 0; i < this.blocks.length && !result; i++)
{
result = this.blocks[i].findBlock(blockId);
}
return result;
}
this.toString = function()
{
return "canvas: " + this.id;
}
}
/*
* Block class
*/
function Block(htmlElement, canvas)
{
/*
* initialization
*/
this.canvas = canvas;
this.htmlElement = htmlElement;
this.id = htmlElement.id;
this.blocks = new Array();
this.moveListeners = new Array();
this.initBlock = function()
{
// inspect block children to identify nested blocks
var children = this.htmlElement.childNodes;
var i;
for(i = 0; i < children.length; i++)
{
if(isBlock(children[i]))
{
var innerBlock = new Block(children[i], this.canvas);
innerBlock.initBlock();
this.blocks.push(innerBlock);
this.moveListeners.push(innerBlock);
}
}
//this.htmlElement.onmousemove = new Function("if(isdrag) findBlock(\"" + this.id + "\").onMove();");
}
this.top = function()
{
return calculateOffsetTop(this.htmlElement);
}
this.left = function()
{
return calculateOffsetLeft(this.htmlElement);
}
this.width = function()
{
return this.htmlElement.offsetWidth;
}
this.height = function()
{
return this.htmlElement.offsetHeight;
}
/*
* methods
*/
this.print = function()
{
var output = "block: " + this.id;
if(this.blocks.length > 0)
{
output += "<ul>";
var i;
for(i = 0; i < this.blocks.length; i++)
{
output += "<li>";
output += this.blocks[i].print();
output += "</li>";
}
output += "</ul>";
}
return output;
}
/*
* This function searches for a nested block (or the block itself) with a given id
*/
this.findBlock = function(blockId)
{
if(this.id == blockId)
return this;
var result;
var i;
for(i = 0; i < this.blocks.length && !result; i++)
{
result = this.blocks[i].findBlock(blockId);
}
return result;
}
this.move = function(left, top)
{
this.htmlElement.style.left = left;
this.htmlElement.style.top = top;
this.onMove();
}
this.onMove = function()
{
var i;
// notify listeners
for(i = 0; i < this.moveListeners.length; i++)
{
this.moveListeners[i].onMove();
}
}
this.toString = function()
{
return "block: " + this.id;
}
}
/*
* Connector class.
* The init function takes two Block objects as arguments representing
* the source and destination of the connector
*/
function Connector(htmlElement, canvas)
{
this.htmlElement = htmlElement;
this.canvas = canvas;
this.source = null;
this.destination = null;
this.startX = null;
this.startY = null;
this.destX = null;
this.destY = null;
this.segment1 = null;
this.segment2 = null;
this.segment3 = null;
this.preferredOrientation = AUTO;
this.orientation = HORIZONTAL;
this.size = 1;
this.color = "black";
this.moveListeners = new Array();
this.initConnector = function()
{
// detect the connector id
if(this.htmlElement.id)
this.id = this.htmlElement.id;
else
this.id = this.htmlElement.className;
// split the class name to get the ids of the source and destination blocks
var splitted = htmlElement.className.split(" ");
if(splitted.length < 3)
{
alert("Unable to create connector \"" + id + "\", class is not in the correct format: connector <sourceBlockId>, <destBlockId>");
return;
}
var connectorClass = splitted[0] + " " + splitted[1] + " " + splitted[2];
this.source = this.canvas.findBlock(splitted[1]);
if(!this.source)
{
alert("cannot find source block with id \"" + splitted[1] + "\"");
return;
}
this.destination = this.canvas.findBlock(splitted[2]);
if(!this.destination)
{
alert("cannot find destination block with id \"" + splitted[2] + "\"");
return;
}
// check preferred orientation
if(hasClass(this.htmlElement, "vertical"))
this.preferredOrientation = VERTICAL;
else if(hasClass(this.htmlElement, "horizontal"))
this.preferredOrientation = HORIZONTAL;
else
this.preferredOrientation = AUTO;
// build the segments
this.segment1 = document.createElement("div");
this.segment1.id = this.id + "_1";
this.canvas.htmlElement.appendChild(this.segment1);
this.segment1.style.position = "absolute";
this.segment1.style.overflow = "hidden";
if(!getStyle(this.segment1, "background-color"))
this.segment1.style.backgroundColor = this.color;
this.segment1.className = connectorClass;
this.segment2 = document.createElement("div");
this.segment2.id = this.id + "_2";
this.canvas.htmlElement.appendChild(this.segment2);
this.segment2.className = connectorClass;
this.segment2.style.position = "absolute";
this.segment2.style.overflow = "hidden";
if(!getStyle(this.segment2, "background-color"))
this.segment2.style.backgroundColor = this.color;
this.segment3 = document.createElement("div");
this.segment3.id = this.id + "_3";
this.canvas.htmlElement.appendChild(this.segment3);
this.segment3.style.position = "absolute";
this.segment3.style.overflow = "hidden";
if(!getStyle(this.segment3, "background-color"))
this.segment3.style.backgroundColor = this.color;
this.segment3.className = connectorClass;
this.repaint();
this.source.moveListeners.push(this);
this.destination.moveListeners.push(this);
// call inspectors for this connector
var i;
for(i = 0; i < inspectors.length; i++)
{
inspectors[i].inspect(this);
}
// remove old html element
this.htmlElement.parentNode.removeChild(this.htmlElement);
}
/**
* Repaints the connector
*/
this.repaint = function()
{
var sourceLeft = this.source.left();
var sourceTop = this.source.top();
var sourceWidth = this.source.width();
var sourceHeight = this.source.height();
var destinationLeft = this.destination.left();
var destinationTop = this.destination.top();
var destinationWidth = this.destination.width();
var destinationHeight = this.destination.height();
if(this.preferredOrientation == HORIZONTAL)
{
// use horizontal orientation except if it is impossible
if((destinationLeft - sourceLeft - sourceWidth) *
(sourceLeft - destinationLeft - destinationWidth) > 0)
this.orientation = VERTICAL;
else
this.orientation = HORIZONTAL;
}
else if(this.preferredOrientation == VERTICAL)
{
// use vertical orientation except if it is impossible
if((destinationTop - sourceTop - sourceHeight) *
(sourceTop - destinationTop - destinationHeight) > 0)
this.orientation = HORIZONTAL;
else
this.orientation = VERTICAL;
}
else
{
// auto orientation: change current orientation if it is impossible to maintain
if(this.orientation == HORIZONTAL &&
(destinationLeft - sourceLeft - sourceWidth) *
(sourceLeft - destinationLeft - destinationWidth) > 0)
{
this.orientation = VERTICAL;
}
else if(this.orientation == VERTICAL &&
(destinationTop - sourceTop - sourceHeight) *
(sourceTop - destinationTop - destinationHeight) > 0)
{
this.orientation = HORIZONTAL;
}
}
if(this.orientation == HORIZONTAL)
{
// deduce which face to use on source and destination blocks
if(sourceLeft + sourceWidth / 2 < destinationLeft + destinationWidth / 2)
{
// use left side of the source block and right side of the destination block
this.startX = sourceLeft + sourceWidth;
this.destX = destinationLeft;
}
else
{
// use right side of the source block and left side of the destination block
this.startX = sourceLeft;
this.destX = destinationLeft + destinationWidth;
}
this.startY = sourceTop + sourceHeight / 2;
this.destY = destinationTop + destinationHeight /2;
// first horizontal segment positioning
this.segment1.style.left = Math.min(this.startX, (this.destX + this.startX) / 2) + "px";
this.segment1.style.top = this.startY + "px";
this.segment1.style.width = Math.abs((this.startX - this.destX) / 2) + this.size + "px";
this.segment1.style.height = this.size + "px";
// vertical segment positioning
this.segment2.style.left = ((this.startX + this.destX) /2) + "px";
this.segment2.style.top = Math.min(this.startY, this.destY) + "px";
this.segment2.style.width = this.size + "px";
this.segment2.style.height = Math.abs(this.destY - this.startY) + "px";
// second horizontal segment positioning
this.segment3.style.left = Math.min((this.startX + this.destX) /2, this.destX) + "px";
this.segment3.style.top = this.destY + "px";
this.segment3.style.width = Math.abs((this.destX - this.startX) / 2) + "px";
this.segment3.style.height = this.size + "px";
// label positioning
//this.htmlElement.style.left = this.startX + "px";
//this.htmlElement.style.top = this.startY + this.size + "px";
}
else
{
// deduce which face to use on source and destination blocks
if(sourceTop + sourceHeight / 2 < destinationTop + destinationHeight / 2)
{
// use bottom side of the sheightblock and top side of thtopestination block
this.startY = sourceTop + sourceHeight;
this.destY = destinationTop;
}
else
{
// use top side of the source block and bottom side of the destination block
this.startY = sourceTop;
this.destY = destinationTop + destinationHeight;
}
this.startX = sourceLeft + sourceWidth / 2;
this.destX = destinationLeft + destinationWidth / 2;
// first vertical segment positioning
this.segment1.style.left = this.startX + "px";
this.segment1.style.top = Math.min(this.startY, (this.destY + this.startY)/2) + "px";
this.segment1.style.width = this.size + "px";
this.segment1.style.height = Math.abs((this.startY - this.destY) / 2) + this.size + "px";
// horizontal segment positioning
this.segment2.style.left = Math.min(this.startX, this.destX) + "px";
this.segment2.style.top = ((this.startY + this.destY) /2) + "px";
this.segment2.style.width = Math.abs(this.destX - this.startX) + "px";
this.segment2.style.height = this.size + "px";
// second vertical segment positioning
this.segment3.style.left = this.destX + "px";
this.segment3.style.top = Math.min(this.destY, (this.destY + this.startY) / 2) + "px";
this.segment3.style.width = this.size + "px";
this.segment3.style.height = Math.abs((this.destY - this.startY) / 2) + "px";
// label positioning
//this.htmlElement.style.left = this.startX + "px";
//this.htmlElement.style.top = this.startY + this.size + "px";
}
}
this.onMove = function()
{
this.repaint();
// notify listeners
var i;
for(i = 0; i < this.moveListeners.length; i++)
this.moveListeners[i].onMove();
}
}
function ConnectorEnd(connector, htmlElement, segment)
{
this.connector = connector;
this.htmlElement = htmlElement;
this.connector.segment1.parentNode.appendChild(htmlElement);
// strip extension
this.src = this.htmlElement.src.substring(0, this.htmlElement.src.lastIndexOf("."));
this.srcExtension = this.htmlElement.src.substring(this.htmlElement.src.lastIndexOf("."));
this.orientation;
this.repaint = function()
{
this.htmlElement.style.position = "absolute";
var orientation;
var left;
var top;
if(connector.orientation == HORIZONTAL)
{
left = segment.offsetLeft;
orientation = "l";
if(segment.offsetLeft == connector.segment2.offsetLeft)
{
left += segment.offsetWidth - this.htmlElement.offsetWidth;
var orientation = "r";
}
top = segment.offsetTop - (this.htmlElement.offsetHeight / 2);
}
else
{
top = segment.offsetTop;
orientation = "u";
if(segment.offsetTop == connector.segment2.offsetTop)
{
top += segment.offsetHeight - this.htmlElement.offsetHeight;
var orientation = "d";
}
left = segment.offsetLeft - (this.htmlElement.offsetWidth / 2);
}
this.htmlElement.style.left = Math.ceil(left) + "px";
this.htmlElement.style.top = Math.ceil(top) + "px";
if(this.htmlElement.tagName.toLowerCase() == "img" && this.orientation != orientation)
{
this.htmlElement.src = this.src + "_" + orientation + this.srcExtension;
}
this.orientation = orientation;
}
this.onMove = function()
{
this.repaint();
}
}
function SideConnectorLabel(connector, htmlElement, side)
{
this.connector = connector;
this.htmlElement = htmlElement;
this.connector.segment1.parentNode.appendChild(htmlElement);
if(side == "source")
this.segment = connector.segment1;
else
this.segment = connector.segment3;
this.side = side;
this.repaint = function()
{
this.htmlElement.style.position = "absolute";
var segmentOrientation;
if(this.segment.offsetWidth < this.segment.offsetHeight)
segmentOrientation = VERTICAL;
else
segmentOrientation = HORIZONTAL;
var left = this.segment.offsetLeft;
var top = this.segment.offsetTop;
if(segmentOrientation == VERTICAL)
{
if(this.segment.offsetTop == connector.segment2.offsetTop)
{
// put label on the bottom of the connector (segment goes downward)
top += this.segment.offsetHeight - this.htmlElement.offsetHeight;
}
}
else
{
if(this.segment.offsetLeft == connector.segment2.offsetLeft)
{
// anchor the label on its right side to avoid overlap with the block
left += this.segment.offsetWidth - this.htmlElement.offsetWidth;
}
if(this.segment.offsetTop < (this.side == "source" ? connector.segment3.offsetTop : connector.segment1.offsetTop))
{
// put label over the connector rather than below
top -= this.htmlElement.offsetHeight;
}
}
this.htmlElement.style.left = Math.ceil(left) + "px";
this.htmlElement.style.top = Math.ceil(top) + "px";
}
this.onMove = function()
{
this.repaint();
}
}
function MiddleConnectorLabel(connector, htmlElement)
{
this.connector = connector;
this.htmlElement = htmlElement;
this.connector.segment2.parentNode.appendChild(htmlElement);
this.repaint = function()
{
this.htmlElement.style.position = "absolute";
var segmentOrientation;
if(connector.segment2.offsetWidth < connector.segment2.offsetHeight)
segmentOrientation = VERTICAL;
else
segmentOrientation = HORIZONTAL;
var left;
var top;
if(segmentOrientation == VERTICAL)
{
// put label at middle height on right side of the connector
top = connector.segment2.offsetTop + (connector.segment2.offsetHeight - this.htmlElement.offsetHeight) / 2;
left = connector.segment2.offsetLeft;
}
else
{
// put connector below the connector at middle widths
top = connector.segment2.offsetTop;
left = connector.segment2.offsetLeft + (connector.segment2.offsetWidth - this.htmlElement.offsetWidth) / 2;;
}
this.htmlElement.style.left = Math.ceil(left) + "px";
this.htmlElement.style.top = Math.ceil(top) + "px";
}
this.onMove = function()
{
this.repaint();
}
}
/*
* Inspector classes
*/
function ConnectorEndsInspector()
{
this.inspect = function(connector)
{
var children = connector.htmlElement.childNodes;
var i;
for(i = 0; i < children.length; i++)
{
if(hasClass(children[i], "connector-end"))
{
var newElement = new ConnectorEnd(connector, children[i], connector.segment3);
newElement.repaint();
connector.moveListeners.push(newElement);
}
else if(hasClass(children[i], "connector-start"))
{
var newElement = new ConnectorEnd(connector, children[i], connector.segment1);
newElement.repaint();
connector.moveListeners.push(newElement);
}
}
}
}
function ConnectorLabelsInspector()
{
this.inspect = function(connector)
{
var children = connector.htmlElement.childNodes;
var i;
for(i = 0; i < children.length; i++)
{
if(hasClass(children[i], "source-label"))
{
var newElement = new SideConnectorLabel(connector, children[i], "source");
newElement.repaint();
connector.moveListeners.push(newElement);
}
else if(hasClass(children[i], "middle-label"))
{
var newElement = new MiddleConnectorLabel(connector, children[i]);
newElement.repaint();
connector.moveListeners.push(newElement);
}
else if(hasClass(children[i], "destination-label"))
{
var newElement = new SideConnectorLabel(connector, children[i], "destination");
newElement.repaint();
connector.moveListeners.push(newElement);
}
}
}
}
/*
* Inspector registration
*/
inspectors.push(new ConnectorEndsInspector());
inspectors.push(new ConnectorLabelsInspector());
/*
* an array containing all the canvases in document
*/
var canvases = new Array();
/*
* This function initializes the js_graph objects inspecting the html document
*/
function initPageObjects()
{
if(isCanvas(document.body))
{
var newCanvas = new Canvas(document.body);
newCanvas.initCanvas();
canvases.push(newCanvas);
}
else
{
var divs = document.getElementsByTagName("div");
var i;
for(i = 0; i < divs.length; i++)
{
if(isCanvas(divs[i]))
{
var newCanvas = new Canvas(divs[i]);
newCanvas.initCanvas();
canvases.push(newCanvas);
}
}
}
}
/*
* Utility functions
*/
function findCanvas(canvasId)
{
var i;
for(i = 0; i < canvases.length; i++)
if(canvases[i].id == canvasId)
return canvases[i];
return null;
}
function findBlock(blockId)
{
var i;
for(i = 0; i < canvases.length; i++)
{
var block = canvases[i].findBlock(blockId);
if(block)
return block;
}
return null;
}
/*
* This function determines whether a html element is to be considered a canvas
*/
function isBlock(htmlElement)
{
return hasClass(htmlElement, "block");
}
/*
* This function determines whether a html element is to be considered a block
*/
function isCanvas(htmlElement)
{
return hasClass(htmlElement, "canvas");
}
/*
* This function determines whether a html element is to be considered a connector
*/
function isConnector(htmlElement)
{
return htmlElement.className && htmlElement.className.match(new RegExp("connector .*"));
}
/*
* This function calculates the absolute "top" value for a html node
*/
function calculateOffsetTop(obj)
{
var curtop = 0;
if (obj.offsetParent)
{
while (obj.offsetParent)
{
curtop += obj.offsetTop;
obj = obj.offsetParent;
}
}
else if (obj.y)
curtop += obj.y;
return curtop;
}
/*
* This function calculates the absolute "left" value for a html node
*/
function calculateOffsetLeft(obj)
{
var curleft = 0;
if (obj.offsetParent)
{
while (obj.offsetParent)
{
curleft += obj.offsetLeft;
obj = obj.offsetParent;
}
}
else if (obj.x)
curleft += obj.x;
return curleft;
}
function hasClass(element, className)
{
if(!element.className)
return false;
var classes = element.className.split(" ");
var i;
for(i = 0; i < classes.length; i++)
if(classes[i] == className)
return true;
return false;
}
/**
* This function retrieves the actual value of a style property even if it is set via css.
*/
function getStyle(node, styleProp)
{
// if not an element
if( node.nodeType != 1)
return;
var value;
if (node.currentStyle)
{
// ie case
styleProp = replaceDashWithCamelNotation(styleProp);
value = node.currentStyle[styleProp];
}
else if (window.getComputedStyle)
{
// mozilla case
value = document.defaultView.getComputedStyle(node, null).getPropertyValue(styleProp);
}
return value;
}
function replaceDashWithCamelNotation(value)
{
var pos = value.indexOf("-");
while(pos > 0 && value.length > pos + 1)
{
value = value.substring(0, pos) + value.substring(pos + 1, pos + 2).toUpperCase() + value.substring(pos + 2);
pos = value.indexOf("-");
}
return value;
}
</script>
<style rel="stylesheet" type="text/css">
.draggable
{
position: absolute;
cursor: move;
}
.connector
{
background-color: black;
}
.dock_point
{
height: 1px;
width: 1px;
overflow: hidden;
padding: 0px !important;
border: none !important;
margin: 0px;
position: absolute;
font-size: 1px;
visibility: hidden;
}
div.block
{
border: 1px solid #262A37;
background-color: #E0E8FF;
padding: 5px;
font-size: 11px;
}
html
{
padding: 0px;
margin: 0px;
}
body
{
font-family: verdana;
color: #33333F;
padding: 3px;
margin: 0px;
background-color: white;
}
h1
{
color: #FF7521;
margin: 0px;
}
h2
{
font-size: 15px;
margin: 0px;
}
.middle-label, .source-label, .destination-label
{
font-size: 11px;
font-weight: bold;
padding: 5px;
}
div.connector
{
background-color: #FF9900;
}
table.main_table
{
width: 100%;
border-collapse: separate;
}
td.menu
{
padding: 5px;
}
.menu ul
{
margin: 0px;
padding: 0px;
list-style-type: none;
list-style-position: outside;
}
.menu li
{
border: none;
padding: 0px;
font-size: 12px;
margin-bottom: 3px;
}
.menu li a
{
display: block;
border: 1px solid #262A37;
width: 100px;
color: #262A37;
text-decoration: none;
padding: 1px;
background-color: #E0E8FF;
}
.menu li a#active_menu
{
color: #FF9900;
border-color: #FF9900;
}
.menu li a:hover
{
color: #FF9900;
border-color: #FF9900;
}
</style>
</head>
<body onload="initPageObjects();">
<table class="main_table">
<tr>
<td style="vertical-align: top; padding: 0px;">
<div id="mainCanvas" class="canvas block" style="width: 100%; height: 400px; background-color: white; padding: 0px;">
<div id="title_block" class="block draggable" style="left: 30px; top: 30px;">
<h1>js-graph.it</h1>
</div>
<div id="subtitle_block" class="block draggable" style="left: 130px; top: 130px;">
<h2>a javascript library for graphs representation</h2>
</div>
<div class="connector title_block subtitle_block">
<img class="connector-end" src="arrow.gif"/>
</div>
<table class="block draggable" style="left: 550px; top: 50px; border-collapse: collapse; cursor: default;" id="sf_logo" cellpadding="0" cellspacing="0">
<tr>
<td style="border: 1px solid #262A37; cursor: pointer;">
<a href="http://www.wbex.ru"><img src="http://www.wbex.ru/style/logo.png" width="125" height="37" border="0" alt="SourceForge.net Logo" /></a></td>
<td></td>
</tr>
<tr>
<td></td>
<td style="border: 1px solid #262A37; cursor: move;">
<img src="move.gif"/></td>
</tr>
</table>
<div class="connector title_block sf_logo">
<label class="middle-label">hosted on</label>
<img class="connector-end" src="arrow.gif"/>
</div>
<div class="connector subtitle_block description1_block">
<img class="connector-end" src="arrow.gif"/>
<label class="middle-label">that</label>
</div>
<div id="description1_block" class="block draggable" style="left: 100px; top: 250px;">
allows you
</div>
<div class="connector description1_block description2_block">
<img class="connector-end" src="arrow.gif"/>
<label class="source-label">to</label>
<label class="middle-label">connect</label>
<label class="destination-label">your</label>
</div>
<div id="description2_block" class="block draggable" style="left: 350px; top: 300px;">
html elements
<div id="description2_out1" class="block dock_point" style="right: -2px; top: 20%; background-color: black;"></div>
<div id="description2_out2" class="block dock_point" style="right: -2px; top: 80%; background-color: black;"></div>
</div>
<div id="no_js_code_block" class="block draggable" style="left: 590px; top: 200px; width: 200px;">
using css classes to declare blocks, connectors, labels...<br/>
no JavaScript code required.
</div>
<div class="connector description2_out1 no_js_code_block horizontal">
<img class="connector-end" src="arrow.gif"/>
<label class="middle-label">how?</label>
</div>
<div id="drag_and_drop_block" class="block draggable" style="left: 600px; top: 340px; width: 250px;">
you can drag and drop anything around!<br/>
(but you already noticed, didn"t you?)
</div>
<div class="connector description2_out2 drag_and_drop_block horizontal">
<img class="connector-end" src="arrow.gif"/>
<label class="middle-label">and...</label>
<label class="destination-label">...oh!</label>
</div>
</div>
</td>
</tr>
</table>
<div class="connector active_menu mainCanvas">
</div>
</body>
</html>
<A href="http://www.wbex.ru/Code/JavaScriptDownload/dragdrop.zip">dragdrop.zip( 15 k)</a>
Drawing arches
<!DOCTYPE html>
<html>
<head>
<title>Arc Test</title>
<style>
body {
text-align: center
}
</style>
<script type="text/javascript">
/*
ExplorerCanvas
Copyright 2006 Google Inc.
-------------------------------------------------------------------------------
DESCRIPTION
Firefox, Safari and Opera 9 support the canvas tag to allow 2D command-based
drawing operations. ExplorerCanvas brings the same functionality to Internet
Explorer; web developers only need to include a single script tag in their
existing canvas webpages to enable this support.
-------------------------------------------------------------------------------
INSTALLATION
Include the ExplorerCanvas tag in the same directory as your HTML files, and
add the following code to your page, preferably in the <head> tag.
If you run into trouble, please look at the included example code to see how
to best implement this
ExplorerCanvas
Google Open Source:
<http://code.google.ru>
<opensource@google.ru>
Developers:
Emil A Eklund <emil@eae.net>
Erik Arvidsson <erik@eae.net>
Glen Murphy <glen@glenmurphy.ru>
*/
// Copyright 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Known Issues:
//
// * Patterns are not implemented.
// * Radial gradient are not implemented. The VML version of these look very
// different from the canvas one.
// * Clipping paths are not implemented.
// * Coordsize. The width and height attribute have higher priority than the
// width and height style values which isn"t correct.
// * Painting mode isn"t implemented.
// * Canvas width/height should is using content-box by default. IE in
// Quirks mode will draw the canvas using border-box. Either change your
// doctype to HTML5
// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
// or use Box Sizing Behavior from WebFX
// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
// * Optimize. There is always room for speed improvements.
// only add this code if we do not already have a canvas implementation
if (!window.CanvasRenderingContext2D) {
(function () {
// alias some functions to make (compiled) code shorter
var m = Math;
var mr = m.round;
var ms = m.sin;
var mc = m.cos;
// this is used for sub pixel precision
var Z = 10;
var Z2 = Z / 2;
var G_vmlCanvasManager_ = {
init: function (opt_doc) {
var doc = opt_doc || document;
if (/MSIE/.test(navigator.userAgent) && !window.opera) {
var self = this;
doc.attachEvent("onreadystatechange", function () {
self.init_(doc);
});
}
},
init_: function (doc) {
if (doc.readyState == "complete") {
// create xmlns
if (!doc.namespaces["g_vml_"]) {
doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
}
// setup default css
var ss = doc.createStyleSheet();
ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
// default size is 300x150 in Gecko and Opera
"text-align:left;width:300px;height:150px}" +
"g_vml_\\:*{behavior:url(#default#VML)}";
// find all canvas elements
var els = doc.getElementsByTagName("canvas");
for (var i = 0; i < els.length; i++) {
if (!els[i].getContext) {
this.initElement(els[i]);
}
}
}
},
fixElement_: function (el) {
// in IE before version 5.5 we would need to add HTML: to the tag name
// but we do not care about IE before version 6
var outerHTML = el.outerHTML;
var newEl = el.ownerDocument.createElement(outerHTML);
// if the tag is still open IE has created the children as siblings and
// it has also created a tag with the name "/FOO"
if (outerHTML.slice(-2) != "/>") {
var tagName = "/" + el.tagName;
var ns;
// remove content
while ((ns = el.nextSibling) && ns.tagName != tagName) {
ns.removeNode();
}
// remove the incorrect closing tag
if (ns) {
ns.removeNode();
}
}
el.parentNode.replaceChild(newEl, el);
return newEl;
},
/**
* Public initializes a canvas element so that it can be used as canvas
* element from now on. This is called automatically before the page is
* loaded but if you are creating elements using createElement you need to
* make sure this is called on the element.
* @param {HTMLElement} el The canvas element to initialize.
* @return {HTMLElement} the element that was created.
*/
initElement: function (el) {
el = this.fixElement_(el);
el.getContext = function () {
if (this.context_) {
return this.context_;
}
return this.context_ = new CanvasRenderingContext2D_(this);
};
// do not use inline function because that will leak memory
el.attachEvent("onpropertychange", onPropertyChange);
el.attachEvent("onresize", onResize);
var attrs = el.attributes;
if (attrs.width && attrs.width.specified) {
// TODO: use runtimeStyle and coordsize
// el.getContext().setWidth_(attrs.width.nodeValue);
el.style.width = attrs.width.nodeValue + "px";
} else {
el.width = el.clientWidth;
}
if (attrs.height && attrs.height.specified) {
// TODO: use runtimeStyle and coordsize
// el.getContext().setHeight_(attrs.height.nodeValue);
el.style.height = attrs.height.nodeValue + "px";
} else {
el.height = el.clientHeight;
}
//el.getContext().setCoordsize_()
return el;
}
};
function onPropertyChange(e) {
var el = e.srcElement;
switch (e.propertyName) {
case "width":
el.style.width = el.attributes.width.nodeValue + "px";
el.getContext().clearRect();
break;
case "height":
el.style.height = el.attributes.height.nodeValue + "px";
el.getContext().clearRect();
break;
}
}
function onResize(e) {
var el = e.srcElement;
if (el.firstChild) {
el.firstChild.style.width = el.clientWidth + "px";
el.firstChild.style.height = el.clientHeight + "px";
}
}
G_vmlCanvasManager_.init();
// precompute "00" to "FF"
var dec2hex = [];
for (var i = 0; i < 16; i++) {
for (var j = 0; j < 16; j++) {
dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
}
}
function createMatrixIdentity() {
return [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]
];
}
function matrixMultiply(m1, m2) {
var result = createMatrixIdentity();
for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
var sum = 0;
for (var z = 0; z < 3; z++) {
sum += m1[x][z] * m2[z][y];
}
result[x][y] = sum;
}
}
return result;
}
function copyState(o1, o2) {
o2.fillStyle = o1.fillStyle;
o2.lineCap = o1.lineCap;
o2.lineJoin = o1.lineJoin;
o2.lineWidth = o1.lineWidth;
o2.miterLimit = o1.miterLimit;
o2.shadowBlur = o1.shadowBlur;
o2.shadowColor = o1.shadowColor;
o2.shadowOffsetX = o1.shadowOffsetX;
o2.shadowOffsetY = o1.shadowOffsetY;
o2.strokeStyle = o1.strokeStyle;
o2.arcScaleX_ = o1.arcScaleX_;
o2.arcScaleY_ = o1.arcScaleY_;
}
function processStyle(styleString) {
var str, alpha = 1;
styleString = String(styleString);
if (styleString.substring(0, 3) == "rgb") {
var start = styleString.indexOf("(", 3);
var end = styleString.indexOf(")", start + 1);
var guts = styleString.substring(start + 1, end).split(",");
str = "#";
for (var i = 0; i < 3; i++) {
str += dec2hex[Number(guts[i])];
}
if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
alpha = guts[3];
}
} else {
str = styleString;
}
return [str, alpha];
}
function processLineCap(lineCap) {
switch (lineCap) {
case "butt":
return "flat";
case "round":
return "round";
case "square":
default:
return "square";
}
}
/**
* This class implements CanvasRenderingContext2D interface as described by
* the WHATWG.
* @param {HTMLElement} surfaceElement The element that the 2D context should
* be associated with
*/
function CanvasRenderingContext2D_(surfaceElement) {
this.m_ = createMatrixIdentity();
this.mStack_ = [];
this.aStack_ = [];
this.currentPath_ = [];
// Canvas context properties
this.strokeStyle = "#000";
this.fillStyle = "#000";
this.lineWidth = 1;
this.lineJoin = "miter";
this.lineCap = "butt";
this.miterLimit = Z * 1;
this.globalAlpha = 1;
this.canvas = surfaceElement;
var el = surfaceElement.ownerDocument.createElement("div");
el.style.width = surfaceElement.clientWidth + "px";
el.style.height = surfaceElement.clientHeight + "px";
el.style.overflow = "hidden";
el.style.position = "absolute";
surfaceElement.appendChild(el);
this.element_ = el;
this.arcScaleX_ = 1;
this.arcScaleY_ = 1;
};
var contextPrototype = CanvasRenderingContext2D_.prototype;
contextPrototype.clearRect = function() {
this.element_.innerHTML = "";
this.currentPath_ = [];
};
contextPrototype.beginPath = function() {
// TODO: Branch current matrix so that save/restore has no effect
// as per safari docs.
this.currentPath_ = [];
};
contextPrototype.moveTo = function(aX, aY) {
this.currentPath_.push({type: "moveTo", x: aX, y: aY});
this.currentX_ = aX;
this.currentY_ = aY;
};
contextPrototype.lineTo = function(aX, aY) {
this.currentPath_.push({type: "lineTo", x: aX, y: aY});
this.currentX_ = aX;
this.currentY_ = aY;
};
contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
aCP2x, aCP2y,
aX, aY) {
this.currentPath_.push({type: "bezierCurveTo",
cp1x: aCP1x,
cp1y: aCP1y,
cp2x: aCP2x,
cp2y: aCP2y,
x: aX,
y: aY});
this.currentX_ = aX;
this.currentY_ = aY;
};
contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
// the following is lifted almost directly from
// http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_);
var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_);
var cp2x = cp1x + (aX - this.currentX_) / 3.0;
var cp2y = cp1y + (aY - this.currentY_) / 3.0;
this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY);
};
contextPrototype.arc = function(aX, aY, aRadius,
aStartAngle, aEndAngle, aClockwise) {
aRadius *= Z;
var arcType = aClockwise ? "at" : "wa";
var xStart = aX + (mc(aStartAngle) * aRadius) - Z2;
var yStart = aY + (ms(aStartAngle) * aRadius) - Z2;
var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2;
var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2;
// IE won"t render arches drawn counter clockwise if xStart == xEnd.
if (xStart == xEnd && !aClockwise) {
xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
// that can be represented in binary
}
this.currentPath_.push({type: arcType,
x: aX,
y: aY,
radius: aRadius,
xStart: xStart,
yStart: yStart,
xEnd: xEnd,
yEnd: yEnd});
};
contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
};
contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
// Will destroy any existing path (same as FF behaviour)
this.beginPath();
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
this.stroke();
};
contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
// Will destroy any existing path (same as FF behaviour)
this.beginPath();
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
this.fill();
};
contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
var gradient = new CanvasGradient_("gradient");
return gradient;
};
contextPrototype.createRadialGradient = function(aX0, aY0,
aR0, aX1,
aY1, aR1) {
var gradient = new CanvasGradient_("gradientradial");
gradient.radius1_ = aR0;
gradient.radius2_ = aR1;
gradient.focus_.x = aX0;
gradient.focus_.y = aY0;
return gradient;
};
contextPrototype.drawImage = function (image, var_args) {
var dx, dy, dw, dh, sx, sy, sw, sh;
// to find the original width we overide the width and height
var oldRuntimeWidth = image.runtimeStyle.width;
var oldRuntimeHeight = image.runtimeStyle.height;
image.runtimeStyle.width = "auto";
image.runtimeStyle.height = "auto";
// get the original size
var w = image.width;
var h = image.height;
// and remove overides
image.runtimeStyle.width = oldRuntimeWidth;
image.runtimeStyle.height = oldRuntimeHeight;
if (arguments.length == 3) {
dx = arguments[1];
dy = arguments[2];
sx = sy = 0;
sw = dw = w;
sh = dh = h;
} else if (arguments.length == 5) {
dx = arguments[1];
dy = arguments[2];
dw = arguments[3];
dh = arguments[4];
sx = sy = 0;
sw = w;
sh = h;
} else if (arguments.length == 9) {
sx = arguments[1];
sy = arguments[2];
sw = arguments[3];
sh = arguments[4];
dx = arguments[5];
dy = arguments[6];
dw = arguments[7];
dh = arguments[8];
} else {
throw "Invalid number of arguments";
}
var d = this.getCoords_(dx, dy);
var w2 = sw / 2;
var h2 = sh / 2;
var vmlStr = [];
var W = 10;
var H = 10;
// For some reason that I"ve now forgotten, using divs didn"t work
vmlStr.push(" <g_vml_:group",
" coordsize="", Z * W, ",", Z * H, """,
" coordorigin="0,0"" ,
" style="width:", W, ";height:", H, ";position:absolute;");
// If filters are necessary (rotation exists), create them
// filters are bog-slow, so only create them if abbsolutely necessary
// The following check doesn"t account for skews (which don"t exist
// in the canvas spec (yet) anyway.
if (this.m_[0][0] != 1 || this.m_[0][1]) {
var filter = [];
// Note the 12/21 reversal
filter.push("M11="", this.m_[0][0], "",",
"M12="", this.m_[1][0], "",",
"M21="", this.m_[0][1], "",",
"M22="", this.m_[1][1], "",",
"Dx="", mr(d.x / Z), "",",
"Dy="", mr(d.y / Z), """);
// Bounding box calculation (need to minimize displayed area so that
// filters don"t waste time on unused pixels.
var max = d;
var c2 = this.getCoords_(dx + dw, dy);
var c3 = this.getCoords_(dx, dy + dh);
var c4 = this.getCoords_(dx + dw, dy + dh);
max.x = Math.max(max.x, c2.x, c3.x, c4.x);
max.y = Math.max(max.y, c2.y, c3.y, c4.y);
vmlStr.push("padding:0 ", mr(max.x / Z), "px ", mr(max.y / Z),
"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
filter.join(""), ", sizingmethod="clip");")
} else {
vmlStr.push("top:", mr(d.y / Z), "px;left:", mr(d.x / Z), "px;")
}
vmlStr.push(" ">" ,
"<g_vml_:image src="", image.src, """,
" style="width:", Z * dw, ";",
" height:", Z * dh, ";"",
" cropleft="", sx / w, """,
" croptop="", sy / h, """,
" cropright="", (w - sx - sw) / w, """,
" cropbottom="", (h - sy - sh) / h, """,
" />",
"</g_vml_:group>");
this.element_.insertAdjacentHTML("BeforeEnd",
vmlStr.join(""));
};
contextPrototype.stroke = function(aFill) {
var lineStr = [];
var lineOpen = false;
var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
var color = a[0];
var opacity = a[1] * this.globalAlpha;
var W = 10;
var H = 10;
lineStr.push("<g_vml_:shape",
" fillcolor="", color, """,
" filled="", Boolean(aFill), """,
" style="position:absolute;width:", W, ";height:", H, ";"",
" coordorigin="0 0" coordsize="", Z * W, " ", Z * H, """,
" stroked="", !aFill, """,
" strokeweight="", this.lineWidth, """,
" strokecolor="", color, """,
" path="");
var newSeq = false;
var min = {x: null, y: null};
var max = {x: null, y: null};
for (var i = 0; i < this.currentPath_.length; i++) {
var p = this.currentPath_[i];
if (p.type == "moveTo") {
lineStr.push(" m ");
var c = this.getCoords_(p.x, p.y);
lineStr.push(mr(c.x), ",", mr(c.y));
} else if (p.type == "lineTo") {
lineStr.push(" l ");
var c = this.getCoords_(p.x, p.y);
lineStr.push(mr(c.x), ",", mr(c.y));
} else if (p.type == "close") {
lineStr.push(" x ");
} else if (p.type == "bezierCurveTo") {
lineStr.push(" c ");
var c = this.getCoords_(p.x, p.y);
var c1 = this.getCoords_(p.cp1x, p.cp1y);
var c2 = this.getCoords_(p.cp2x, p.cp2y);
lineStr.push(mr(c1.x), ",", mr(c1.y), ",",
mr(c2.x), ",", mr(c2.y), ",",
mr(c.x), ",", mr(c.y));
} else if (p.type == "at" || p.type == "wa") {
lineStr.push(" ", p.type, " ");
var c = this.getCoords_(p.x, p.y);
var cStart = this.getCoords_(p.xStart, p.yStart);
var cEnd = this.getCoords_(p.xEnd, p.yEnd);
lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",",
mr(c.y - this.arcScaleY_ * p.radius), " ",
mr(c.x + this.arcScaleX_ * p.radius), ",",
mr(c.y + this.arcScaleY_ * p.radius), " ",
mr(cStart.x), ",", mr(cStart.y), " ",
mr(cEnd.x), ",", mr(cEnd.y));
}
// TODO: Following is broken for curves due to
// move to proper paths.
// Figure out dimensions so we can do gradient fills
// properly
if(c) {
if (min.x == null || c.x < min.x) {
min.x = c.x;
}
if (max.x == null || c.x > max.x) {
max.x = c.x;
}
if (min.y == null || c.y < min.y) {
min.y = c.y;
}
if (max.y == null || c.y > max.y) {
max.y = c.y;
}
}
}
lineStr.push(" ">");
if (typeof this.fillStyle == "object") {
var focus = {x: "50%", y: "50%"};
var width = (max.x - min.x);
var height = (max.y - min.y);
var dimension = (width > height) ? width : height;
focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%";
var colors = [];
// inside radius (%)
if (this.fillStyle.type_ == "gradientradial") {
var inside = (this.fillStyle.radius1_ / dimension * 100);
// percentage that outside radius exceeds inside radius
var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
} else {
var inside = 0;
var expansion = 100;
}
var insidecolor = {offset: null, color: null};
var outsidecolor = {offset: null, color: null};
// We need to sort "colors" by percentage, from 0 > 100 otherwise ie
// won"t interpret it correctly
this.fillStyle.colors_.sort(function (cs1, cs2) {
return cs1.offset - cs2.offset;
});
for (var i = 0; i < this.fillStyle.colors_.length; i++) {
var fs = this.fillStyle.colors_[i];
colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");
if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
insidecolor.offset = fs.offset;
insidecolor.color = fs.color;
}
if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
outsidecolor.offset = fs.offset;
outsidecolor.color = fs.color;
}
}
colors.pop();
lineStr.push("<g_vml_:fill",
" color="", outsidecolor.color, """,
" color2="", insidecolor.color, """,
" type="", this.fillStyle.type_, """,
" focusposition="", focus.x, ", ", focus.y, """,
" colors="", colors.join(""), """,
" opacity="", opacity, "" />");
} else if (aFill) {
lineStr.push("<g_vml_:fill color="", color, "" opacity="", opacity, "" />");
} else {
lineStr.push(
"<g_vml_:stroke",
" opacity="", opacity,""",
" joinstyle="", this.lineJoin, """,
" miterlimit="", this.miterLimit, """,
" endcap="", processLineCap(this.lineCap) ,""",
" weight="", this.lineWidth, "px"",
" color="", color,"" />"
);
}
lineStr.push("</g_vml_:shape>");
this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));
this.currentPath_ = [];
};
contextPrototype.fill = function() {
this.stroke(true);
}
contextPrototype.closePath = function() {
this.currentPath_.push({type: "close"});
};
/**
* @private
*/
contextPrototype.getCoords_ = function(aX, aY) {
return {
x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2,
y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2
}
};
contextPrototype.save = function() {
var o = {};
copyState(this, o);
this.aStack_.push(o);
this.mStack_.push(this.m_);
this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
};
contextPrototype.restore = function() {
copyState(this.aStack_.pop(), this);
this.m_ = this.mStack_.pop();
};
contextPrototype.translate = function(aX, aY) {
var m1 = [
[1, 0, 0],
[0, 1, 0],
[aX, aY, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
contextPrototype.rotate = function(aRot) {
var c = mc(aRot);
var s = ms(aRot);
var m1 = [
[c, s, 0],
[-s, c, 0],
[0, 0, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
contextPrototype.scale = function(aX, aY) {
this.arcScaleX_ *= aX;
this.arcScaleY_ *= aY;
var m1 = [
[aX, 0, 0],
[0, aY, 0],
[0, 0, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
/******** STUBS ********/
contextPrototype.clip = function() {
// TODO: Implement
};
contextPrototype.arcTo = function() {
// TODO: Implement
};
contextPrototype.createPattern = function() {
return new CanvasPattern_;
};
// Gradient / Pattern Stubs
function CanvasGradient_(aType) {
this.type_ = aType;
this.radius1_ = 0;
this.radius2_ = 0;
this.colors_ = [];
this.focus_ = {x: 0, y: 0};
}
CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
aColor = processStyle(aColor);
this.colors_.push({offset: 1-aOffset, color: aColor});
};
function CanvasPattern_() {}
// set up externs
G_vmlCanvasManager = G_vmlCanvasManager_;
CanvasRenderingContext2D = CanvasRenderingContext2D_;
CanvasGradient = CanvasGradient_;
CanvasPattern = CanvasPattern_;
})();
} // if
</script>
<script>
window.onload = function() {
var ctx = document.getElementById("c").getContext("2d");
ctx.beginPath();
ctx.arc(25, 25, 20, 0, Math.PI, false);
ctx.stroke();
ctx.save();
ctx.scale(0.5, 0.5);
ctx.beginPath();
ctx.arc(75, 25, 20, 0, Math.PI * 2, false);
ctx.stroke();
ctx.restore();
ctx.beginPath();
ctx.arc(25, 75, 20, 0, Math.PI, true);
ctx.stroke();
ctx.beginPath();
ctx.arc(75, 75, 20, 0, Math.PI * 2, true);
ctx.stroke();
};
</script>
</head>
<body>
<canvas id=c width=200 height=100></canvas>
<p>This tests that drawing arches work in the same way in different
browsers.</p>
</body>
</html>
JavaScript Graphics
<html>
<head>
<title>JavaScriptGraphics</title>
<style type="text/css">
<!--
input.active {
border-width: 1;
border-style: solid;
border-color: #000000;
}
input.passive {
color: #C0C0C0;
border-width: 0;
border-style: solid;
border-color: #000000;
}
-->
</style>
<script type="text/javascript">
/*
JavaScriptGraphics v 0.6
Pixel graphics in Javascript.
Copyright (C) 2003 Kitya Karlson http://www.karlson.ru/, karlson@karlson.ru
Tested in Microsoft Internet Explorer 6 and Mozilla 1.3.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation in version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*! \mainpage JavaScriptGraphics
*
* \section intro Introduction
* <p>Color image is just a 2D array of colors. If you think about image this way you can see
* that it is possible to draw an image of the size N*M in HTML-only way - as a table with
* N columns and M rows, where each cell takes one pixel and has a background color assigned
* to it. Unfortunately even a small image represented like this in HTML results in a large
* and complex code for the browser. But for artifitial images it is very easy to use RLE
* compression - if there are several cells in a line of the same color you can
* replace them by one cell with the correct colspan/rowspan attributes assigned for it.
* <p>There are three cool things about this type of images:
* <ol>
* <li>They can be posted on the pages where images are not allowed (like some forums, or
* livejournal),
* <li>The size of HTML sended from the web server to client"s computer is not very large -
* the HTML for the images is generated on the client"s computer only,
* <li>They can be animated to react on user input.
* </ol>
* <p>I made a simple JavaScript library that allows you to use simple 2D graphics functions
* to create such images (like drawing lines, points or circles). Comments and suggestions are <a href="mailto:karlson@karlson.ru">welcome</a>!
* <p>As an alternative output method a handling of output to a Java applet is also provided in
* addition to a plain HTML rendering.
* <p>This library was tested in Microsoft Internet Explorer 6 and Mozilla 1.3.
*
* \section examples Examples
* <ul>
* <li><a href="../tests/clock.html">Analog Clock</a> - shows the difference between different types
* of HTML rendering
* <li><a href="../tests/lines.html">Color Sun</a> - shows an example of zooming in HTML output (Java output zooming is working in the same way).
* <li><a href="../tests/eyes.html">Eyes</a> - eyes that follow your mouse pointer.
* <li><a href="../tests/ontop.html">On Top</a> - DHTML output overlay example.
* <li><a href="../tests/plot.html">Sin/Cos Plot</a> - shows an example of HTML rendering and Java Applet processing (works only in Mozilla or in IE with Java virtual machine from SUN).
* </ul>
*
* \section conv Converter
* <a href="../tests/makeimage.php">Image to HTML converter</a> - this converter contains a preprocessing step, which is made using PHP and GD. Color dithering is produce to reduce the output complexity.
*
* \section changes Changes
* <p><b>v 0.6</b>
* <ul>
* <li>Image to HTML image converter added.
* </ul>
* <p><b>v 0.5</b>
* <ul>
* <li>Polygon and polyline drawing functions are added.
* <li>DHTML output option and overlay output options + invisible color are added to HTML output processor.
* <li>New example ("On Top") demonstrating new DHTML output options is added.
* </ul>
* <p><b>v 0.4</b>
* <ul>
* <li>Java Applet output methods are introduced in addition to HTML output methods.
* <li>Color values are now accepted in several formats.
* <li>Rendering time is calculated now.
* <li>Examples are updated to reflect new features.
* <li>A lot of bugfixes.
* </ul>
* <p><b>v 0.3</b>
* <ul>
* <li>Small bugfixes.
* <li>HTML output processor is moved to the separate class.
* <li>An optimised method of output compression - Optimised RLE - is introduced. It is using
* both colspan and rowspan attributes, dividing the table into the minimum number of cells.
* It is not so fast as the fast simple RLE, but it makes the tables really small. Which method
* is used for compression (Fast RLE or Optimised RLE) is controlled by the compression
* parametr of the HTML output class.
* </ul>
* <p><b>v 0.2</b>
* <ul>
* <li>Functions are rewritten as a class and moved to the separate file.
* <li>Area fill function is rewritten using stack instead of recursion - this allows large closed areas to be filled-in.
* <li>Code is cleaned up and documented using Doxygen.
* </ul>
* <p><b>v 0.1</b>
* <ul>
* <li>Initial release.
* </ul>
*
* \section downloads Downloads
* <ul>
* <li>Download <a href="../jsgraphics.0.6.zip">JavaScriptGraphics v 0.6</a> - latest.
* <li>Download <a href="../jsgraphics.0.5.zip">JavaScriptGraphics v 0.5</a>.
* <li>Download <a href="../jsgraphics.0.4.zip">JavaScriptGraphics v 0.4</a>.
* <li>Download <a href="../jsgraphics.0.3.zip">JavaScriptGraphics v 0.3</a>.
* <li>Download <a href="../jsgraphics.0.2.zip">JavaScriptGraphics v 0.2</a>.
* <li>Download <a href="../jsgraphics.0.1.zip">JavaScriptGraphics v 0.1</a>.
* </ul>
*
* \section legal Legal
* <p>This is <b>JavaScriptGraphics</b> library written in 2003 by Kitya Karlson <a href="mailto:karlson@karlson.ru">karlson@karlson.ru</a>.
* This software is distributed under <a href="http://www.gnu.org/licenses/lgpl.html">LGPL</a>.
*
* \section warning Warning
* <p>After working on this project for a couple of weeks I have found out that <a href="http://www.walterzorn.ru/jsgraphics/jsgraphics_e.htm">a simillar
* attempt</a> was made before already. The main differnce with my approach and the approach
* taken by Walter Zorn is that my method performs drawing on offscreen first (on array)
* and then creates optimised html only when flushed. Also in my method three types of
* output are supported (HTML table, DHTML and Java Applet) and not only one output method like
* in Walter"s class. So my method would work faster and provide better output for more complex
* images and is more suitable for animation, however Walter"s method works faster if you
* are in need of just one line.
*
*/
/**
* @file
* JavaScriptGraphics is a library for producing graphics using JavaScript
* by manipulating HTML tables. It uses "run length encoding" by taking
* advantage of colspan attributes in order to reduce the complexity of
* the output. Images created in this manner can be posted on the pages
* such as forums or LiveJournal where images are not allowed, and can
* be animated using JavaScript.
* The methods provided allow to draw lines, point, circles, ellipsoids and other
* geometrical figures.
*/
/**
* JSColor class provides functions for converting different color repersentations
* (HTML, RGB, INT) into each other. All methods of this class could be used as "static".
*
* Examples:
*
* HTML: #000000 - black, #FFFFFF - white,
*
* RGB: 0,0,0 - black, 255,255,255 - white,
*
* INT: 0 - black, 16777215 - white.
*
* @ctor
* Constructs JSColor class (empty).
*/
function JSColor() {
};
/**
* Converts RGB color to HTML color.
* @tparam Integer red Red component of the color.
* @tparam Integer green Green component of the color.
* @tparam Integer blue Blue component of the color.
* @treturn String HTML color.
*/
JSColor.prototype.rgbtohtml = function (red,green,blue) {
x="0123456789ABCDEF";
return "#" + x.charAt(red >> 4)+x.charAt(red & 15) + x.charAt(green >> 4)+x.charAt(green & 15) + x.charAt(blue >> 4) + x.charAt(blue & 15);
};
/**
* Converts INT color to HTML color.
* @tparam Integer rgb Color value.
* @treturn String HTML color.
*/
JSColor.prototype.inttohtml = function(rgb) {
return this.rgbtohtml( ((rgb >> 16) & 0xff), ((rgb >> 8) & 0xff ), (rgb & 0xff) );
};
/**
* Converts HTML color to INT color.
* @tparam String html HTML color.
* @treturn Integer Color value.
*/
JSColor.prototype.htmltoint = function(html) {
x="0123456789ABCDEF";
html = html.toUpperCase();
red = 16*x.indexOf(html.charAt(1))+x.indexOf(html.charAt(2));
green = 16*x.indexOf(html.charAt(3))+x.indexOf(html.charAt(4));
blue = 16*x.indexOf(html.charAt(5))+x.indexOf(html.charAt(6));
return (red << 16) | (green << 8) | blue;
};
/**
* Converts RGB color to INT color.
* @tparam Integer red Red component of the color.
* @tparam Integer green Green component of the color.
* @tparam Integer blue Blue component of the color.
* @treturn Integer Color value.
*/
JSColor.prototype.rgbtoint = function(red,green,blue) {
return (red << 16) | (green << 8) | blue;
};
/**
* "Static" Color object.
* @type JSColor
*/
var Color = new JSColor();
/**
* Simple 2D graphics canvas.
*
* x=0,y=0 - top left corner of the canvas.
* x=width-1,y=height-1 - bottom right corner of the canvas.
*
* @ctor
* Constructs a 2D image drawing canvas.
* @tparam Integer width The width of the canvas.
* @tparam Integer height The height of the canvas.
* @tparam Integer bgcolor The background color of the canvas.
*/
function GCanvas(width, height, bgcolor) {
/**
* The width of the canvas.
* @type Integer
*/
this.width=((width>0)?width:0) || 35;
/**
* The height of the canvas.
* @type Integer
*/
this.height=((height>0)?height:0) || 35;
/**
* The background color of the canvas (HTML format string).
* @type String
*/
this.bgcolor=bgcolor || 0;
/**
* Internal array representing the image canvas.
* @type Array
*/
this.image = new Array(this.height*this.width);
for (i=0;i<this.height*this.width;i++) {
this.image[i]=this.bgcolor;
}
};
/**
* Clears the whole canvas using default background color.
*/
GCanvas.prototype.clear = function() {
for (i=0; i < this.height*this.width; i++) {
this.image[i]=this.bgcolor;
}
};
/**
* Puts a pixel of the defined color in the position x,y.
* @tparam Integer x X coordinate of the pixel.
* @tparam Integer y Y coordinate of the pixel.
* @tparam Integer color The color of the pixel.
*/
GCanvas.prototype.draw = function(x,y,color) {
if ((x >= 0) && (y >= 0) && (y < this.height) && (x < this.width)) {
this.image[y*this.width+x]=color;
}
};
/**
* Gets a color of a pixel in the position x,y
* @treturn Integer Color of the pixel.
*/
GCanvas.prototype.getcolor = function(x,y) {
if ((x >= 0) && (y >= 0) && (y < this.height) && (x < this.width)) {
return this.image[y*this.width+x];
} else {
return null;
}
};
/**
* Draws a line (Bresenham"s algorithm).
* @tparam Integer x1 X coordinate of the start pixel.
* @tparam Integer y1 Y coordinate of the start pixel.
* @tparam Integer x2 X coordinate of the ending pixel.
* @tparam Integer y2 Y coordinate of the ending pixel.
* @tparam Integer color The color of the line.
*/
GCanvas.prototype.line = function(x1, y1, x2, y2, color)
{
var pX=(x1<x2) ? 1 : -1;
var pY=(y1<y2) ? 1 : -1;
var E;
var Delta1;
var Delta2;
var X=x1;
var Y=y1;
var I=1;
var temp;
if (x1>x2) { temp = x1; x1=x2; x2=temp; }
if (y1>y2) { temp = y1; y1=y2; y2=temp; }
var dX=x2-x1;
var dY=y2-y1;
this.draw(X, Y, color);
if (dX>=dY)
{
Delta1=dY<<1;
Delta2=(dY-dX)<<1;
E=Delta1-dX;
for (X+=pX; I<=dX; I++, X+=pX)
{
if (E>0)
{
E+=Delta2;
Y+=pY;
}
else E+=Delta1;
this.draw(X, Y, color);
}
}
else
{
Delta1=dX<<1;
Delta2=(dX-dY)<<1;
E=Delta1-dY;
for (Y+=pY; I<=dY; I++, Y+=pY)
{
if (E>0)
{
E+=Delta2;
X+=pX;
}
else E+=Delta1;
this.draw(X,Y,color);
}
}
};
/**
* Draws a circle (Bresenham"s algorithm).
* @tparam Integer xc X coordinate of the center of the circle.
* @tparam Integer yc Y coordinate of the center of the circle.
* @tparam Integer r The radius of the circle.
* @tparam Integer color The color of the circle.
*/
GCanvas.prototype.circle = function(xc,yc,r,color) {
var y = r;
var x = 0;
var d = 3 - 2*r;
while (x <= y) {
this.draw(x+xc,y+yc,color);
this.draw(x+xc,-y+yc,color);
this.draw(-x+xc,-y+yc,color);
this.draw(-x+xc,y+yc,color);
this.draw(y+xc,x+yc,color);
this.draw(y+xc,-x+yc,color);
this.draw(-y+xc,-x+yc,color);
this.draw(-y+xc,x+yc,color);
if (d < 0) {
d = d + 4*x +6;
} else {
d = d + 4*(x-y) + 10;
y = y-1;
}
x = x+1;
}
};
/**
* Draws an ellipse (Bresenham"s algorithm).
* @tparam Integer xc X coordinate of the center of the circle.
* @tparam Integer yc Y coordinate of the center of the circle.
* @tparam Integer a The semi-axis of the ellipse.
* @tparam Integer b The semi-axis of the ellipse.
* @tparam Integer color The color of the ellipse.
*/
GCanvas.prototype.ellipse = function(xc,yc,a,b,color)
{
b_square=b*b;
a_square=a*a;
row=b;
col=0;
two_a_square=a_square<<1;
four_a_square=a_square<<2;
four_b_square=b_square<<2;
two_b_square=b_square<<1;
d=two_a_square*((row-1)*(row))+a_square+two_b_square*(1-a_square);
while(a_square*(row)>b_square*(col))
{
this.draw(col+xc, row+yc, color);
this.draw(col+xc, yc-row, color);
this.draw(xc-col, row+yc, color);
this.draw(xc-col, yc-row, color);
if (d>=0)
{
row--;
d-=four_a_square*(row);
}
d+=two_b_square*(3+(col<<1));
col++;
}
d=two_b_square*(col+1)*col+two_a_square*(row*(row-2)+1)+(1-two_a_square)*b_square;
while ((row) + 1)
{
this.draw(col+xc, row+yc, color);
this.draw(col+xc, yc-row, color);
this.draw(xc-col, row+yc, color);
this.draw(xc-col, yc-row, color);
if (d<=0)
{
col++;
d+=four_b_square*col;
}
row--;
d+=two_a_square*(3-(row <<1));
}
};
/**
* Fills a closed area (using stack)
* @tparam Integer x X coordinate of the point inside the area to be filled-in.
* @tparam Integer y Y coordinate of the point inside the area to be filled-in.
* @tparam Integer color Fill color.
*/
GCanvas.prototype.fill = function(x,y,color) {
stack_head=0;
stack_tail=0;
floodfill_stackx = new Array((this.width+2)*(this.height+2));
floodfill_stacky = new Array((this.width+2)*(this.height+2));
clr=this.getcolor(x,y);
floodfill_stackx[stack_head]=x;
floodfill_stacky[stack_head]=y;
this.draw(x,y,color);
stack_head++;
while ( (stack_head<((this.width+2)*(this.height+2))) && (stack_head>stack_tail) ) {
x=floodfill_stackx[stack_tail];
y=floodfill_stacky[stack_tail];
stack_tail++;
if (x>=0 && y>=0 && x<this.width && y<this.height) {
if (this.getcolor(x+1,y)==clr) {
floodfill_stackx[stack_head]=x+1;
floodfill_stacky[stack_head]=y;
this.draw(x+1,y,color);
stack_head++;
}
if (this.getcolor(x-1,y)==clr) {
floodfill_stackx[stack_head]=x-1;
floodfill_stacky[stack_head]=y;
this.draw(x-1,y,color);
stack_head++;
}
if (this.getcolor(x,y+1)==clr) {
floodfill_stackx[stack_head]=x;
floodfill_stacky[stack_head]=y+1;
this.draw(x,y+1,color);
stack_head++;
}
if (this.getcolor(x,y-1)==clr) {
floodfill_stackx[stack_head]=x;
floodfill_stacky[stack_head]=y-1;
this.draw(x,y-1,color);
stack_head++;
}
}
}
delete floodfill_stacky;
delete floodfill_stackx;
};
/**
* Draws a polyline.
* @tparam Array x x1,x2, ..., xn.
* @tparam Array y y1,y2, ..., yn.
* @tparam Integer color Polyline color.
*/
GCanvas.prototype.polyline = function(x, y, color) {
var z = x.length-1; while (z >= 0) this.line(x[z], y[z], x[--z], y[z], color);
};
/**
* Draws a polygon (automatically closed if last points are not identical.
* @tparam Array x x1,x2, ..., xn.
* @tparam Array y y1,y2, ..., yn.
* @tparam Integer color Polygon color.
*/
GCanvas.prototype.polygon = function(x, y, color) {
this.polyline(x, y, color);
this.line(x[x.length-1], y[x.length-1], x[0], y[0], color);
};
/**
* Output processor.
*
* An abstract output processor.
*
* @ctor
* Abstract output processor.
* @tparam Integer scale The scaling of the output (1 = 1x = no scaling).
*/
function GOutput(scale) {
/**
* Scaling of the output (1 = 1x = no scaling).
* @type Integer
*/
this.scale=scale || 1;
}
/**
* HTML output processor.
*
* This output processor can be used to render the canvas as an HTML table.
* Two types ("Fast RLE" and "Optimised RLE") of output rendering are provided,
* see bellow.
*
* @ctor
* Constructs an HTML output processor.
*/
function GHTMLOutput() {
/**
* Compression parametr (0 - fast RLE, 1 - optimised RLE).
* @type Integer
*/
this.rupression=0;
/**
* Output type - HTML (table) or DHTML (div"s). If dhtml is set to false HTML output
* is produced and if dhtml is set to true DHTML output is produced.
* @type Boolean
*/
this.dhtml = true;
/**
* An invsibile color. By default invisible_color = -1, i.e. the default background of the canvas.
* @type Integer
*/
this.invisible_color = -1;
/**
* Number of cells generated in the HTML table.
* @type Integer
*/
this.number_of_cells=0;
/**
* Time (in ms.) used for the generation of the HTML table.
* @type Integer
*/
this.generation_time=0;
/**
* Javascript document object (usually this.document).
*
* Needed only for print functions.
* @type Object
*/
this.doc = null;
/**
* Output layer ID.
*
* Needed only for print functions.
* @type String
*/
this.layerId = null;
/**
* Append or overwrite the layer.
*
* Needed only for print functions.
* @type Boolean
*/
this.append = false;
}
GHTMLOutput.prototype = new GOutput();
/**
* HTML output printing parametrs setup function.
*
* @tparam Object doc Document object (usually this.document).
* @tparam String layerId Output layer ID.
*/
GHTMLOutput.prototype.setup = function(doc,layerId) {
this.doc=doc;
this.layerId=layerId;
};
/**
* Returns the image canvas html (using RLE compression on lines = fast RLE).
* @treturn String A table in HTML format representing the image canvas.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.get_html = function(gcanvas) {
time_now = new Date();
this.number_of_cells = 0;
if (this.invisible_color == -1) {
inv_color = gcanvas.bgcolor;
} else {
inv_color = this.invisibile_color;
}
str = new String("");
len = 0;
if (! this.dhtml) {
str += "<table border=0 cellspacing=0 cellpadding=0 width="+gcanvas.width*this.scale+" height="+gcanvas.height*this.scale+">";
}
for (i=0; i < gcanvas.height; i++) {
if (! this.dhtml) {
str += "<tr height="+this.scale+" width="+gcanvas.width*this.scale+">";
}
current_color = gcanvas.getcolor(0,i);
len = 0;
start_j = 0;
for (j=0; j < gcanvas.width; j++) {
if ( (gcanvas.getcolor(j,i) != current_color) || (j == gcanvas.width-1)) {
if (j== gcanvas.width-1) { len++; }
if (! this.dhtml) {
str += "<td width="+this.scale*len+" height="+this.scale + ( (len>1) ? " colspan="+len : "" ) + ( (current_color!=inv_color) ? " bgcolor="+Color.inttohtml(current_color) : "") + "></td>";
} else {
if (current_color!=inv_color) {
str += "<div style="position:absolute;"+ "left:" + (start_j*this.scale) + "px;"+ "top:" + (i*this.scale) + "px;"+ "width:" + (this.scale*len) + "px;"+ "height:" + this.scale + "px;"+ "clip:rect(0,"+(this.scale*len)+"px,"+this.scale+"px,0);" + "overflow:hidden;background-color:" + Color.inttohtml(current_color) + ";" + ""><\/div>";
}
}
this.number_of_cells++;
len=1;
start_j = j;
current_color=gcanvas.getcolor(j,i);
} else {
len++;
}
}
if (! this.dhtml) {
str += "</tr>";
}
}
if (! this.dhtml) {
str += "</table>";
}
time_finish = new Date();
this.generation_time = time_finish - time_now;
delete time_now;
delete time_finish;
return str;
};
/**
* Returns the image canvas html (using RLE compression on both lines and rows = optimised RLE).
* @treturn String A table in HTML format representing the image canvas.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.get_optimised_html = function(gcanvas) {
time_now = new Date();
if (this.invisible_color == -1) {
inv_color = gcanvas.bgcolor;
} else {
inv_color = this.invisibile_color;
}
this.number_of_cells = 0;
str = new String("");
flushed = new Array(gcanvas.height*gcanvas.width);
for (i=0;i<gcanvas.height*gcanvas.width;i++) {
flushed[i]=0;
}
if (! this.dhtml) {
str += "<table border=0 cellspacing=0 cellpadding=0 width="+gcanvas.width*this.scale+" height="+gcanvas.height*this.scale+">";
}
for (i=0; i < gcanvas.height; i++) {
if (! this.dhtml) {
str += "<tr height="+this.scale+" width="+gcanvas.width*this.scale+">";
}
for (j=0; j < gcanvas.width; j++) {
if (flushed[i*gcanvas.width+j] == 0) {
current_color = gcanvas.getcolor(j,i);
k=gcanvas.height;
opt = 0;
colspan = 1;
rowspan = 1;
for (x=j; x < gcanvas.width; x++) {
if (flushed[i*gcanvas.height+x]==1) { break; }
if (gcanvas.getcolor(x,i) != current_color) { break; }
for (y=i; y < k; y++) {
if (flushed[y*gcanvas.width+x]==1) { break; }
if (gcanvas.getcolor(x,y) != current_color) { break; }
}
if (y-1<0) { break; }
if (gcanvas.getcolor(x,y-1) != current_color) { break; }
k=y;
if ( ((x-j+1)*(y-i)) > opt) {
opt=(x-j+1)*(y-i);
colspan = x-j+1;
rowspan = y-i;
}
}
for (y=i; y < i+rowspan; y++) {
for (x=j; x < j+colspan; x++) {
flushed[y*gcanvas.width+x]=1;
}
}
if (! this.dhtml) {
str += "<td width="+this.scale*colspan+" height="+ this.scale*rowspan + ( (colspan>1) ? " colspan="+colspan : "" ) + ( (rowspan > 1) ? " rowspan=" + rowspan : "") + ( (current_color!=inv_color) ? " bgcolor="+Color.inttohtml(current_color) : "") + "></td>";
} else {
if (current_color!=inv_color) {
str += "<div style="position:absolute;"+ "left:" + (j*this.scale) + "px;"+ "top:" + (i*this.scale) + "px;"+ "width:" + (this.scale*colspan) + "px;"+ "height:" + (this.scale*rowspan) + "px;"+ "clip:rect(0,"+(this.scale*colspan)+"px,"+(this.scale*rowspan)+"px,0);" + "overflow:hidden;background-color:" + Color.inttohtml(current_color) + ";" + ""><\/div>";
}
}
this.number_of_cells++;
}
}
if (! this.dhtml) {
str += "</tr>";
}
}
if (! this.dhtml) {
str += "</table>";
}
delete flushed;
time_finish = new Date();
this.generation_time = time_finish - time_now;
delete time_now;
delete time_finish;
return str;
};
/**
* Assigns the image canvas html (using RLE compression on lines = fast RLE) to a given layer.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.print_html = function(gcanvas) {
if ((this.doc != null) && (this.layerId != null)) {
if (this.doc.all) {
outlayer = this.doc.all[this.layerId];
}
if (this.doc.getElementById) {
outlayer = this.doc.getElementById(this.layerId);
}
if (this.append) {
outlayer.innerHTML += this.get_html(gcanvas);
} else {
outlayer.innerHTML = this.get_html(gcanvas);
}
}
};
/**
* Assigns the image canvas html (using RLE compression on both lines and rows = optimised RLE) to a given layer.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.print_optimised_html = function(gcanvas) {
if ((this.doc != null) && (this.layerId != null)) {
if (this.doc.all) {
outlayer = this.doc.all[this.layerId];
}
if (this.doc.getElementById) {
outlayer = this.doc.getElementById(this.layerId);
}
if (this.append) {
outlayer.innerHTML += this.get_optimised_html(gcanvas);
} else {
outlayer.innerHTML = this.get_optimised_html(gcanvas);
}
}
};
/**
* Returns the image canvas html
* @treturn String A table in HTML format representing the image canvas.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.get = function(gcanvas) {
switch (this.rupression) {
case 0: return this.get_html(gcanvas); break;
case 1: return this.get_optimised_html(gcanvas); break;
default: return this.get_html(gcanvas); break;
}
};
/**
* Assigns the image canvas html to a given layer.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.print = function(gcanvas) {
switch (this.rupression) {
case 0: return this.print_html(gcanvas); break;
case 1: return this.print_optimised_html(gcanvas); break;
default: return this.print_html(gcanvas); break;
}
}
/**
* Java (applet) output processor.
*
* This output processor can be used to pass your canvas to a Java applet for rendering.
*
* @ctor
* Constructs an applet output processor.
*/
function GJavaOutput() {
/**
* Time (in ms.) used for the generation of the image string to be passed to the applet.
* @type Integer
*/
this.generation_time=0;
/**
* Javascript document object (usually this.document).
*
* Needed only for print function.
* @type Object
*/
this.doc = null;
/**
* The name of an applet providing setImage function.
*
* Example of an applet providing setImage function:
* <PRE>
* -----------------------------------------------------------
*
* import java.applet.Applet;
* import java.awt.Graphics;
* import java.awt.Image;
* import java.awt.ruponent;
* import java.lang.Integer;
* import java.util.StringTokenizer;
*
* public class ImageOutput extends Applet {
* Image JSImage = null;
*
* public void init() {
* // some initialisation here
* }
*
* public void paint(Graphics g) {
* if (this.JSImage != null) {
* g.drawImage(this.JSImage, 0, 0, this);
* }
* }
*
* public void setImage(int w, int h, String pixels) {
* int pix[] = new int[w * h];
* StringTokenizer st = new StringTokenizer(pixels," ");
* int index = 0;
* while (st.hasMoreTokens()) {
* pix[index++]=Integer.parseInt(st.nextToken());
* }
* this.JSImage = createImage(new java.awt.image.MemoryImageSource(w, h, pix, 0, w));
* repaint();
* }
* }
*
* -----------------------------------------------------------
* </PRE>
* Javascript is used to passed a String of the image bytes separated by space. Array would
* be a better choice, but it seems that MS IE fails to pass JavaScript Array to Java correctly.
*
* Needed only for print function.
* @type String
*/
this.appletName = null;
/**
* Alpha chanel value.
* @type Integer
*/
this.alpha = 255;
}
GJavaOutput.prototype = new GOutput();
/**
* Java output printing parametrs setup function (needed only for print functions).
*
* @tparam Object doc Document object (usually this.document).
* @tparam String appletName Reciving java applet name.
*/
GJavaOutput.prototype.setup = function(doc,appletName) {
this.doc=doc;
this.appletName=appletName;
};
/**
* Returns the image canvas string to be passed to Java.
* @treturn String String representing the bytes of the image separated by spaces;
* @tparam GCanvas gcanvas 2D image canvas.
*/
GJavaOutput.prototype.get = function(gcanvas) {
time_now = new Date();
pixels = new String("");
for (y=0;y<gcanvas.height;y++) {
for (i=0;i<this.scale;i++) {
for (x=0;x<gcanvas.width;x++) {
for (j=0;j<this.scale;j++) {
pixels += (pixels.length>0?" ":"") + ((this.alpha << 24) | gcanvas.getcolor(x,y));
}
}
}
}
time_finish = new Date();
this.generation_time = time_finish - time_now;
delete time_now;
delete time_finish;
return pixels;
};
/**
* Passes the image canvas String to a given applet.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GJavaOutput.prototype.print = function(gcanvas) {
if ((this.doc != null) && (this.appletName != null)) {
this.doc.applets[this.appletName].setImage(gcanvas.width*this.scale,gcanvas.height*this.scale,this.get(gcanvas));
}
};
</script>
<script type="text/javascript">
<!--
// Usage examples
var white = Color.htmltoint("#FFFFFF");
var black = Color.htmltoint("#000000");
var gc = new GCanvas(35,35,white,1);
var output = new GHTMLOutput();
// Analog clock
var clock_runs = 0;
var cenx = Math.round(gc.width/2.)-1;
var ceny = Math.round(gc.height/2.)-1;
var rad = Math.min(Math.round(gc.width/2.),Math.round(gc.height/2.))-3;
var shcolor = Color.htmltoint("#FF0000");
var mhcolor = Color.htmltoint("#00FF00");
var hhcolor = Color.htmltoint("#0000FF");
function clock()
{
datetime = new Date();
offset = -datetime.getTimezoneOffset()/60;
dst = 1;
s = datetime.getSeconds();
m = datetime.getMinutes();
h = datetime.getHours() % 12;
//h = datetime.getHours() - offset + dst;
//if ( h<0 ) { h = 24 + h; }
//if ( h>=24 ) { h = h - 24; }
//h = h % 12;
// Clear the background.
gc.clear();
// Draw the clock circle.
gc.circle(cenx, ceny, rad, black);
gc.circle(cenx, ceny, 3, black);
// Draw the second pointer.
sr = (s/60)*2*Math.PI-.5*Math.PI;
sx = Math.round(rad*Math.cos(sr)+cenx);
sy = Math.round(rad*Math.sin(sr)+ceny);
csx = Math.round(3*Math.cos(sr)+cenx);
csy = Math.round(3*Math.sin(sr)+ceny);
gc.line(csx,csy, sx, sy, shcolor);
gc.circle(sx, sy, 2, shcolor);
// Draw the minute hand.
mr=(m/60+s/3600)*2*Math.PI-.5*Math.PI;
cmx = Math.round(3*Math.cos(mr)+cenx);
cmy = Math.round(3*Math.sin(mr)+ceny);
mx = Math.round((rad*0.9)*Math.cos(mr)+cenx);
my = Math.round((rad*0.9)*Math.sin(mr)+ceny);
gc.line(cmx, cmy - 1, mx, my, mhcolor);
gc.line(cmx - 1, cmy, mx, my, mhcolor);
// Draw the hour hand.
hr=(h/12+m/720)*2*Math.PI-.5*Math.PI;
chx = Math.round(3*Math.cos(hr)+cenx);
chy = Math.round(3*Math.sin(hr)+ceny);
hx = Math.round((rad*0.7)*Math.cos(hr)+cenx);
hy = Math.round((rad*0.7)*Math.sin(hr)+ceny);
gc.line(chx, chy - 1, hx, hy, hhcolor);
gc.line(chx - 1, chy, hx, hy, hhcolor);
output.setup(this.document,"clock");
output.rupression=0;
output.print(gc);
if (this.document.all) {
this.document.all["clock_cells"].innerHTML = output.number_of_cells;
this.document.all["clock_time"].innerHTML = output.generation_time;
}
if (this.document.getElementById) {
this.document.getElementById("clock_cells").innerHTML = output.number_of_cells;
this.document.getElementById("clock_time").innerHTML = output.generation_time;
}
output.setup(this.document,"clock_optimised");
output.rupression=1;
output.print(gc);
if (this.document.all) {
this.document.all["clock_optimised_cells"].innerHTML = output.number_of_cells;
this.document.all["clock_optimised_time"].innerHTML = output.generation_time;
}
if (this.document.getElementById) {
this.document.getElementById("clock_optimised_cells").innerHTML = output.number_of_cells;
this.document.getElementById("clock_optimised_time").innerHTML = output.generation_time;
}
if (clock_runs == 1) {
setTimeout("clock()", 1000);
}
}
// -->
</script>
</head>
<body onLoad="clock(); return true;">
<h1>Analog Clock</h1>
<p>This eaxample shows the difference between two different HTML rendering methods. First method
"Fast RLE" uses RLE compression (by means of colspan attribute of the table cells) on lines
only. Second method is "Optimised RLE" - on each step it is trying to find a largest possible
square area of the same color and produces a single cell for it by manipulating both colspan
and rowspan attributes of the table.
<p>Actually which method is faster depends on the implementation of JavaScript in your browser.
I have noticed that "Fast RLE" is usually works faster for me in Microsoft Internet Explorer and
"Optimised RLE" works faster in Mozilla.
<p>Notation: <font color="#FF0000">red hand</font> - seconds, <font color="#00FF00">green hand</font> - minutes, <font color="#0000FF">blue hand</font> - hours.
<form name="clock_start">
Click <input type="button" value="Start!" name="start_clock" class="active" onClick="if (clock_runs==0) { this.form.start_clock.className="passive"; this.form.stop_clock.className="active"; clock_runs=1; clock(); }"> to start the clock.
Click <input type="button" value="Stop!" name="stop_clock" class="passive" onClick="this.form.start_clock.className="active"; this.form.stop_clock.className="passive"; clock_runs=0;"> to stop the clock.
</form>
<table border="0" cellpadding=4 cellspacing=0 bgcolor="#ffffff" style="border: 1px dotted rgb(0,0,0)">
<tr height=35>
<th height=35> </td>
<th height=35>Fast RLE</td>
<th height=35>Optimised RLE</div>
</tr>
<tr height=35>
<td height=35> </td>
<td height=35><div id="clock" style="position:relative;top:0;left:0;height:35;width:35;">[Fast RLE]</div></td>
<td height=35><div id="clock_optimised" style="position:relative;top:0;left:0;height:35;width:35;">[Optimised RLE]</div></td>
</tr>
<tr height=35>
<td height=35>Number of cells in the output<br>(out of total 35x35=1225)</td>
<td height=35><div id="clock_cells" style="position:relative;top:0;left:0;height:35;width:35;"></div></td>
<td height=35><div id="clock_optimised_cells" style="position:relative;top:0;left:0;height:35;width:35;"></div></td>
</tr>
<tr height=35>
<td height=35>Rendering time (in ms.)</td>
<td height=35><div id="clock_time" style="position:relative;top:0;left:0;height:35;width:35;"></div></td>
<td height=35><div id="clock_optimised_time" style="position:relative;top:0;left:0;height:35;width:35;"></div></td>
</tr>
</table>
<p>
</body>
</html>
JavaScript Graphics 2
<html>
<head>
<title>JavaScriptGraphics</title>
<style type="text/css">
<!--
input.active {
border-width: 1;
border-style: solid;
border-color: #000000;
}
input.passive {
color: #C0C0C0;
border-width: 0;
border-style: solid;
border-color: #000000;
}
-->
</style>
<script type="text/javascript">
/*
JavaScriptGraphics v 0.6
Pixel graphics in Javascript.
Copyright (C) 2003 Kitya Karlson http://www.karlson.ru/, karlson@karlson.ru
Tested in Microsoft Internet Explorer 6 and Mozilla 1.3.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation in version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*! \mainpage JavaScriptGraphics
*
* \section intro Introduction
* <p>Color image is just a 2D array of colors. If you think about image this way you can see
* that it is possible to draw an image of the size N*M in HTML-only way - as a table with
* N columns and M rows, where each cell takes one pixel and has a background color assigned
* to it. Unfortunately even a small image represented like this in HTML results in a large
* and complex code for the browser. But for artifitial images it is very easy to use RLE
* compression - if there are several cells in a line of the same color you can
* replace them by one cell with the correct colspan/rowspan attributes assigned for it.
* <p>There are three cool things about this type of images:
* <ol>
* <li>They can be posted on the pages where images are not allowed (like some forums, or
* livejournal),
* <li>The size of HTML sended from the web server to client"s computer is not very large -
* the HTML for the images is generated on the client"s computer only,
* <li>They can be animated to react on user input.
* </ol>
* <p>I made a simple JavaScript library that allows you to use simple 2D graphics functions
* to create such images (like drawing lines, points or circles). Comments and suggestions are <a href="mailto:karlson@karlson.ru">welcome</a>!
* <p>As an alternative output method a handling of output to a Java applet is also provided in
* addition to a plain HTML rendering.
* <p>This library was tested in Microsoft Internet Explorer 6 and Mozilla 1.3.
*
* \section examples Examples
* <ul>
* <li><a href="../tests/clock.html">Analog Clock</a> - shows the difference between different types
* of HTML rendering
* <li><a href="../tests/lines.html">Color Sun</a> - shows an example of zooming in HTML output (Java output zooming is working in the same way).
* <li><a href="../tests/eyes.html">Eyes</a> - eyes that follow your mouse pointer.
* <li><a href="../tests/ontop.html">On Top</a> - DHTML output overlay example.
* <li><a href="../tests/plot.html">Sin/Cos Plot</a> - shows an example of HTML rendering and Java Applet processing (works only in Mozilla or in IE with Java virtual machine from SUN).
* </ul>
*
* \section conv Converter
* <a href="../tests/makeimage.php">Image to HTML converter</a> - this converter contains a preprocessing step, which is made using PHP and GD. Color dithering is produce to reduce the output complexity.
*
* \section changes Changes
* <p><b>v 0.6</b>
* <ul>
* <li>Image to HTML image converter added.
* </ul>
* <p><b>v 0.5</b>
* <ul>
* <li>Polygon and polyline drawing functions are added.
* <li>DHTML output option and overlay output options + invisible color are added to HTML output processor.
* <li>New example ("On Top") demonstrating new DHTML output options is added.
* </ul>
* <p><b>v 0.4</b>
* <ul>
* <li>Java Applet output methods are introduced in addition to HTML output methods.
* <li>Color values are now accepted in several formats.
* <li>Rendering time is calculated now.
* <li>Examples are updated to reflect new features.
* <li>A lot of bugfixes.
* </ul>
* <p><b>v 0.3</b>
* <ul>
* <li>Small bugfixes.
* <li>HTML output processor is moved to the separate class.
* <li>An optimised method of output compression - Optimised RLE - is introduced. It is using
* both colspan and rowspan attributes, dividing the table into the minimum number of cells.
* It is not so fast as the fast simple RLE, but it makes the tables really small. Which method
* is used for compression (Fast RLE or Optimised RLE) is controlled by the compression
* parametr of the HTML output class.
* </ul>
* <p><b>v 0.2</b>
* <ul>
* <li>Functions are rewritten as a class and moved to the separate file.
* <li>Area fill function is rewritten using stack instead of recursion - this allows large closed areas to be filled-in.
* <li>Code is cleaned up and documented using Doxygen.
* </ul>
* <p><b>v 0.1</b>
* <ul>
* <li>Initial release.
* </ul>
*
* \section downloads Downloads
* <ul>
* <li>Download <a href="../jsgraphics.0.6.zip">JavaScriptGraphics v 0.6</a> - latest.
* <li>Download <a href="../jsgraphics.0.5.zip">JavaScriptGraphics v 0.5</a>.
* <li>Download <a href="../jsgraphics.0.4.zip">JavaScriptGraphics v 0.4</a>.
* <li>Download <a href="../jsgraphics.0.3.zip">JavaScriptGraphics v 0.3</a>.
* <li>Download <a href="../jsgraphics.0.2.zip">JavaScriptGraphics v 0.2</a>.
* <li>Download <a href="../jsgraphics.0.1.zip">JavaScriptGraphics v 0.1</a>.
* </ul>
*
* \section legal Legal
* <p>This is <b>JavaScriptGraphics</b> library written in 2003 by Kitya Karlson <a href="mailto:karlson@karlson.ru">karlson@karlson.ru</a>.
* This software is distributed under <a href="http://www.gnu.org/licenses/lgpl.html">LGPL</a>.
*
* \section warning Warning
* <p>After working on this project for a couple of weeks I have found out that <a href="http://www.walterzorn.ru/jsgraphics/jsgraphics_e.htm">a simillar
* attempt</a> was made before already. The main differnce with my approach and the approach
* taken by Walter Zorn is that my method performs drawing on offscreen first (on array)
* and then creates optimised html only when flushed. Also in my method three types of
* output are supported (HTML table, DHTML and Java Applet) and not only one output method like
* in Walter"s class. So my method would work faster and provide better output for more complex
* images and is more suitable for animation, however Walter"s method works faster if you
* are in need of just one line.
*
*/
/**
* @file
* JavaScriptGraphics is a library for producing graphics using JavaScript
* by manipulating HTML tables. It uses "run length encoding" by taking
* advantage of colspan attributes in order to reduce the complexity of
* the output. Images created in this manner can be posted on the pages
* such as forums or LiveJournal where images are not allowed, and can
* be animated using JavaScript.
* The methods provided allow to draw lines, point, circles, ellipsoids and other
* geometrical figures.
*/
/**
* JSColor class provides functions for converting different color repersentations
* (HTML, RGB, INT) into each other. All methods of this class could be used as "static".
*
* Examples:
*
* HTML: #000000 - black, #FFFFFF - white,
*
* RGB: 0,0,0 - black, 255,255,255 - white,
*
* INT: 0 - black, 16777215 - white.
*
* @ctor
* Constructs JSColor class (empty).
*/
function JSColor() {
};
/**
* Converts RGB color to HTML color.
* @tparam Integer red Red component of the color.
* @tparam Integer green Green component of the color.
* @tparam Integer blue Blue component of the color.
* @treturn String HTML color.
*/
JSColor.prototype.rgbtohtml = function (red,green,blue) {
x="0123456789ABCDEF";
return "#" + x.charAt(red >> 4)+x.charAt(red & 15) + x.charAt(green >> 4)+x.charAt(green & 15) + x.charAt(blue >> 4) + x.charAt(blue & 15);
};
/**
* Converts INT color to HTML color.
* @tparam Integer rgb Color value.
* @treturn String HTML color.
*/
JSColor.prototype.inttohtml = function(rgb) {
return this.rgbtohtml( ((rgb >> 16) & 0xff), ((rgb >> 8) & 0xff ), (rgb & 0xff) );
};
/**
* Converts HTML color to INT color.
* @tparam String html HTML color.
* @treturn Integer Color value.
*/
JSColor.prototype.htmltoint = function(html) {
x="0123456789ABCDEF";
html = html.toUpperCase();
red = 16*x.indexOf(html.charAt(1))+x.indexOf(html.charAt(2));
green = 16*x.indexOf(html.charAt(3))+x.indexOf(html.charAt(4));
blue = 16*x.indexOf(html.charAt(5))+x.indexOf(html.charAt(6));
return (red << 16) | (green << 8) | blue;
};
/**
* Converts RGB color to INT color.
* @tparam Integer red Red component of the color.
* @tparam Integer green Green component of the color.
* @tparam Integer blue Blue component of the color.
* @treturn Integer Color value.
*/
JSColor.prototype.rgbtoint = function(red,green,blue) {
return (red << 16) | (green << 8) | blue;
};
/**
* "Static" Color object.
* @type JSColor
*/
var Color = new JSColor();
/**
* Simple 2D graphics canvas.
*
* x=0,y=0 - top left corner of the canvas.
* x=width-1,y=height-1 - bottom right corner of the canvas.
*
* @ctor
* Constructs a 2D image drawing canvas.
* @tparam Integer width The width of the canvas.
* @tparam Integer height The height of the canvas.
* @tparam Integer bgcolor The background color of the canvas.
*/
function GCanvas(width, height, bgcolor) {
/**
* The width of the canvas.
* @type Integer
*/
this.width=((width>0)?width:0) || 35;
/**
* The height of the canvas.
* @type Integer
*/
this.height=((height>0)?height:0) || 35;
/**
* The background color of the canvas (HTML format string).
* @type String
*/
this.bgcolor=bgcolor || 0;
/**
* Internal array representing the image canvas.
* @type Array
*/
this.image = new Array(this.height*this.width);
for (i=0;i<this.height*this.width;i++) {
this.image[i]=this.bgcolor;
}
};
/**
* Clears the whole canvas using default background color.
*/
GCanvas.prototype.clear = function() {
for (i=0; i < this.height*this.width; i++) {
this.image[i]=this.bgcolor;
}
};
/**
* Puts a pixel of the defined color in the position x,y.
* @tparam Integer x X coordinate of the pixel.
* @tparam Integer y Y coordinate of the pixel.
* @tparam Integer color The color of the pixel.
*/
GCanvas.prototype.draw = function(x,y,color) {
if ((x >= 0) && (y >= 0) && (y < this.height) && (x < this.width)) {
this.image[y*this.width+x]=color;
}
};
/**
* Gets a color of a pixel in the position x,y
* @treturn Integer Color of the pixel.
*/
GCanvas.prototype.getcolor = function(x,y) {
if ((x >= 0) && (y >= 0) && (y < this.height) && (x < this.width)) {
return this.image[y*this.width+x];
} else {
return null;
}
};
/**
* Draws a line (Bresenham"s algorithm).
* @tparam Integer x1 X coordinate of the start pixel.
* @tparam Integer y1 Y coordinate of the start pixel.
* @tparam Integer x2 X coordinate of the ending pixel.
* @tparam Integer y2 Y coordinate of the ending pixel.
* @tparam Integer color The color of the line.
*/
GCanvas.prototype.line = function(x1, y1, x2, y2, color)
{
var pX=(x1<x2) ? 1 : -1;
var pY=(y1<y2) ? 1 : -1;
var E;
var Delta1;
var Delta2;
var X=x1;
var Y=y1;
var I=1;
var temp;
if (x1>x2) { temp = x1; x1=x2; x2=temp; }
if (y1>y2) { temp = y1; y1=y2; y2=temp; }
var dX=x2-x1;
var dY=y2-y1;
this.draw(X, Y, color);
if (dX>=dY)
{
Delta1=dY<<1;
Delta2=(dY-dX)<<1;
E=Delta1-dX;
for (X+=pX; I<=dX; I++, X+=pX)
{
if (E>0)
{
E+=Delta2;
Y+=pY;
}
else E+=Delta1;
this.draw(X, Y, color);
}
}
else
{
Delta1=dX<<1;
Delta2=(dX-dY)<<1;
E=Delta1-dY;
for (Y+=pY; I<=dY; I++, Y+=pY)
{
if (E>0)
{
E+=Delta2;
X+=pX;
}
else E+=Delta1;
this.draw(X,Y,color);
}
}
};
/**
* Draws a circle (Bresenham"s algorithm).
* @tparam Integer xc X coordinate of the center of the circle.
* @tparam Integer yc Y coordinate of the center of the circle.
* @tparam Integer r The radius of the circle.
* @tparam Integer color The color of the circle.
*/
GCanvas.prototype.circle = function(xc,yc,r,color) {
var y = r;
var x = 0;
var d = 3 - 2*r;
while (x <= y) {
this.draw(x+xc,y+yc,color);
this.draw(x+xc,-y+yc,color);
this.draw(-x+xc,-y+yc,color);
this.draw(-x+xc,y+yc,color);
this.draw(y+xc,x+yc,color);
this.draw(y+xc,-x+yc,color);
this.draw(-y+xc,-x+yc,color);
this.draw(-y+xc,x+yc,color);
if (d < 0) {
d = d + 4*x +6;
} else {
d = d + 4*(x-y) + 10;
y = y-1;
}
x = x+1;
}
};
/**
* Draws an ellipse (Bresenham"s algorithm).
* @tparam Integer xc X coordinate of the center of the circle.
* @tparam Integer yc Y coordinate of the center of the circle.
* @tparam Integer a The semi-axis of the ellipse.
* @tparam Integer b The semi-axis of the ellipse.
* @tparam Integer color The color of the ellipse.
*/
GCanvas.prototype.ellipse = function(xc,yc,a,b,color)
{
b_square=b*b;
a_square=a*a;
row=b;
col=0;
two_a_square=a_square<<1;
four_a_square=a_square<<2;
four_b_square=b_square<<2;
two_b_square=b_square<<1;
d=two_a_square*((row-1)*(row))+a_square+two_b_square*(1-a_square);
while(a_square*(row)>b_square*(col))
{
this.draw(col+xc, row+yc, color);
this.draw(col+xc, yc-row, color);
this.draw(xc-col, row+yc, color);
this.draw(xc-col, yc-row, color);
if (d>=0)
{
row--;
d-=four_a_square*(row);
}
d+=two_b_square*(3+(col<<1));
col++;
}
d=two_b_square*(col+1)*col+two_a_square*(row*(row-2)+1)+(1-two_a_square)*b_square;
while ((row) + 1)
{
this.draw(col+xc, row+yc, color);
this.draw(col+xc, yc-row, color);
this.draw(xc-col, row+yc, color);
this.draw(xc-col, yc-row, color);
if (d<=0)
{
col++;
d+=four_b_square*col;
}
row--;
d+=two_a_square*(3-(row <<1));
}
};
/**
* Fills a closed area (using stack)
* @tparam Integer x X coordinate of the point inside the area to be filled-in.
* @tparam Integer y Y coordinate of the point inside the area to be filled-in.
* @tparam Integer color Fill color.
*/
GCanvas.prototype.fill = function(x,y,color) {
stack_head=0;
stack_tail=0;
floodfill_stackx = new Array((this.width+2)*(this.height+2));
floodfill_stacky = new Array((this.width+2)*(this.height+2));
clr=this.getcolor(x,y);
floodfill_stackx[stack_head]=x;
floodfill_stacky[stack_head]=y;
this.draw(x,y,color);
stack_head++;
while ( (stack_head<((this.width+2)*(this.height+2))) && (stack_head>stack_tail) ) {
x=floodfill_stackx[stack_tail];
y=floodfill_stacky[stack_tail];
stack_tail++;
if (x>=0 && y>=0 && x<this.width && y<this.height) {
if (this.getcolor(x+1,y)==clr) {
floodfill_stackx[stack_head]=x+1;
floodfill_stacky[stack_head]=y;
this.draw(x+1,y,color);
stack_head++;
}
if (this.getcolor(x-1,y)==clr) {
floodfill_stackx[stack_head]=x-1;
floodfill_stacky[stack_head]=y;
this.draw(x-1,y,color);
stack_head++;
}
if (this.getcolor(x,y+1)==clr) {
floodfill_stackx[stack_head]=x;
floodfill_stacky[stack_head]=y+1;
this.draw(x,y+1,color);
stack_head++;
}
if (this.getcolor(x,y-1)==clr) {
floodfill_stackx[stack_head]=x;
floodfill_stacky[stack_head]=y-1;
this.draw(x,y-1,color);
stack_head++;
}
}
}
delete floodfill_stacky;
delete floodfill_stackx;
};
/**
* Draws a polyline.
* @tparam Array x x1,x2, ..., xn.
* @tparam Array y y1,y2, ..., yn.
* @tparam Integer color Polyline color.
*/
GCanvas.prototype.polyline = function(x, y, color) {
var z = x.length-1; while (z >= 0) this.line(x[z], y[z], x[--z], y[z], color);
};
/**
* Draws a polygon (automatically closed if last points are not identical.
* @tparam Array x x1,x2, ..., xn.
* @tparam Array y y1,y2, ..., yn.
* @tparam Integer color Polygon color.
*/
GCanvas.prototype.polygon = function(x, y, color) {
this.polyline(x, y, color);
this.line(x[x.length-1], y[x.length-1], x[0], y[0], color);
};
/**
* Output processor.
*
* An abstract output processor.
*
* @ctor
* Abstract output processor.
* @tparam Integer scale The scaling of the output (1 = 1x = no scaling).
*/
function GOutput(scale) {
/**
* Scaling of the output (1 = 1x = no scaling).
* @type Integer
*/
this.scale=scale || 1;
}
/**
* HTML output processor.
*
* This output processor can be used to render the canvas as an HTML table.
* Two types ("Fast RLE" and "Optimised RLE") of output rendering are provided,
* see bellow.
*
* @ctor
* Constructs an HTML output processor.
*/
function GHTMLOutput() {
/**
* Compression parametr (0 - fast RLE, 1 - optimised RLE).
* @type Integer
*/
this.rupression=0;
/**
* Output type - HTML (table) or DHTML (div"s). If dhtml is set to false HTML output
* is produced and if dhtml is set to true DHTML output is produced.
* @type Boolean
*/
this.dhtml = true;
/**
* An invsibile color. By default invisible_color = -1, i.e. the default background of the canvas.
* @type Integer
*/
this.invisible_color = -1;
/**
* Number of cells generated in the HTML table.
* @type Integer
*/
this.number_of_cells=0;
/**
* Time (in ms.) used for the generation of the HTML table.
* @type Integer
*/
this.generation_time=0;
/**
* Javascript document object (usually this.document).
*
* Needed only for print functions.
* @type Object
*/
this.doc = null;
/**
* Output layer ID.
*
* Needed only for print functions.
* @type String
*/
this.layerId = null;
/**
* Append or overwrite the layer.
*
* Needed only for print functions.
* @type Boolean
*/
this.append = false;
}
GHTMLOutput.prototype = new GOutput();
/**
* HTML output printing parametrs setup function.
*
* @tparam Object doc Document object (usually this.document).
* @tparam String layerId Output layer ID.
*/
GHTMLOutput.prototype.setup = function(doc,layerId) {
this.doc=doc;
this.layerId=layerId;
};
/**
* Returns the image canvas html (using RLE compression on lines = fast RLE).
* @treturn String A table in HTML format representing the image canvas.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.get_html = function(gcanvas) {
time_now = new Date();
this.number_of_cells = 0;
if (this.invisible_color == -1) {
inv_color = gcanvas.bgcolor;
} else {
inv_color = this.invisibile_color;
}
str = new String("");
len = 0;
if (! this.dhtml) {
str += "<table border=0 cellspacing=0 cellpadding=0 width="+gcanvas.width*this.scale+" height="+gcanvas.height*this.scale+">";
}
for (i=0; i < gcanvas.height; i++) {
if (! this.dhtml) {
str += "<tr height="+this.scale+" width="+gcanvas.width*this.scale+">";
}
current_color = gcanvas.getcolor(0,i);
len = 0;
start_j = 0;
for (j=0; j < gcanvas.width; j++) {
if ( (gcanvas.getcolor(j,i) != current_color) || (j == gcanvas.width-1)) {
if (j== gcanvas.width-1) { len++; }
if (! this.dhtml) {
str += "<td width="+this.scale*len+" height="+this.scale + ( (len>1) ? " colspan="+len : "" ) + ( (current_color!=inv_color) ? " bgcolor="+Color.inttohtml(current_color) : "") + "></td>";
} else {
if (current_color!=inv_color) {
str += "<div style="position:absolute;"+ "left:" + (start_j*this.scale) + "px;"+ "top:" + (i*this.scale) + "px;"+ "width:" + (this.scale*len) + "px;"+ "height:" + this.scale + "px;"+ "clip:rect(0,"+(this.scale*len)+"px,"+this.scale+"px,0);" + "overflow:hidden;background-color:" + Color.inttohtml(current_color) + ";" + ""><\/div>";
}
}
this.number_of_cells++;
len=1;
start_j = j;
current_color=gcanvas.getcolor(j,i);
} else {
len++;
}
}
if (! this.dhtml) {
str += "</tr>";
}
}
if (! this.dhtml) {
str += "</table>";
}
time_finish = new Date();
this.generation_time = time_finish - time_now;
delete time_now;
delete time_finish;
return str;
};
/**
* Returns the image canvas html (using RLE compression on both lines and rows = optimised RLE).
* @treturn String A table in HTML format representing the image canvas.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.get_optimised_html = function(gcanvas) {
time_now = new Date();
if (this.invisible_color == -1) {
inv_color = gcanvas.bgcolor;
} else {
inv_color = this.invisibile_color;
}
this.number_of_cells = 0;
str = new String("");
flushed = new Array(gcanvas.height*gcanvas.width);
for (i=0;i<gcanvas.height*gcanvas.width;i++) {
flushed[i]=0;
}
if (! this.dhtml) {
str += "<table border=0 cellspacing=0 cellpadding=0 width="+gcanvas.width*this.scale+" height="+gcanvas.height*this.scale+">";
}
for (i=0; i < gcanvas.height; i++) {
if (! this.dhtml) {
str += "<tr height="+this.scale+" width="+gcanvas.width*this.scale+">";
}
for (j=0; j < gcanvas.width; j++) {
if (flushed[i*gcanvas.width+j] == 0) {
current_color = gcanvas.getcolor(j,i);
k=gcanvas.height;
opt = 0;
colspan = 1;
rowspan = 1;
for (x=j; x < gcanvas.width; x++) {
if (flushed[i*gcanvas.height+x]==1) { break; }
if (gcanvas.getcolor(x,i) != current_color) { break; }
for (y=i; y < k; y++) {
if (flushed[y*gcanvas.width+x]==1) { break; }
if (gcanvas.getcolor(x,y) != current_color) { break; }
}
if (y-1<0) { break; }
if (gcanvas.getcolor(x,y-1) != current_color) { break; }
k=y;
if ( ((x-j+1)*(y-i)) > opt) {
opt=(x-j+1)*(y-i);
colspan = x-j+1;
rowspan = y-i;
}
}
for (y=i; y < i+rowspan; y++) {
for (x=j; x < j+colspan; x++) {
flushed[y*gcanvas.width+x]=1;
}
}
if (! this.dhtml) {
str += "<td width="+this.scale*colspan+" height="+ this.scale*rowspan + ( (colspan>1) ? " colspan="+colspan : "" ) + ( (rowspan > 1) ? " rowspan=" + rowspan : "") + ( (current_color!=inv_color) ? " bgcolor="+Color.inttohtml(current_color) : "") + "></td>";
} else {
if (current_color!=inv_color) {
str += "<div style="position:absolute;"+ "left:" + (j*this.scale) + "px;"+ "top:" + (i*this.scale) + "px;"+ "width:" + (this.scale*colspan) + "px;"+ "height:" + (this.scale*rowspan) + "px;"+ "clip:rect(0,"+(this.scale*colspan)+"px,"+(this.scale*rowspan)+"px,0);" + "overflow:hidden;background-color:" + Color.inttohtml(current_color) + ";" + ""><\/div>";
}
}
this.number_of_cells++;
}
}
if (! this.dhtml) {
str += "</tr>";
}
}
if (! this.dhtml) {
str += "</table>";
}
delete flushed;
time_finish = new Date();
this.generation_time = time_finish - time_now;
delete time_now;
delete time_finish;
return str;
};
/**
* Assigns the image canvas html (using RLE compression on lines = fast RLE) to a given layer.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.print_html = function(gcanvas) {
if ((this.doc != null) && (this.layerId != null)) {
if (this.doc.all) {
outlayer = this.doc.all[this.layerId];
}
if (this.doc.getElementById) {
outlayer = this.doc.getElementById(this.layerId);
}
if (this.append) {
outlayer.innerHTML += this.get_html(gcanvas);
} else {
outlayer.innerHTML = this.get_html(gcanvas);
}
}
};
/**
* Assigns the image canvas html (using RLE compression on both lines and rows = optimised RLE) to a given layer.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.print_optimised_html = function(gcanvas) {
if ((this.doc != null) && (this.layerId != null)) {
if (this.doc.all) {
outlayer = this.doc.all[this.layerId];
}
if (this.doc.getElementById) {
outlayer = this.doc.getElementById(this.layerId);
}
if (this.append) {
outlayer.innerHTML += this.get_optimised_html(gcanvas);
} else {
outlayer.innerHTML = this.get_optimised_html(gcanvas);
}
}
};
/**
* Returns the image canvas html
* @treturn String A table in HTML format representing the image canvas.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.get = function(gcanvas) {
switch (this.rupression) {
case 0: return this.get_html(gcanvas); break;
case 1: return this.get_optimised_html(gcanvas); break;
default: return this.get_html(gcanvas); break;
}
};
/**
* Assigns the image canvas html to a given layer.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.print = function(gcanvas) {
switch (this.rupression) {
case 0: return this.print_html(gcanvas); break;
case 1: return this.print_optimised_html(gcanvas); break;
default: return this.print_html(gcanvas); break;
}
}
/**
* Java (applet) output processor.
*
* This output processor can be used to pass your canvas to a Java applet for rendering.
*
* @ctor
* Constructs an applet output processor.
*/
function GJavaOutput() {
/**
* Time (in ms.) used for the generation of the image string to be passed to the applet.
* @type Integer
*/
this.generation_time=0;
/**
* Javascript document object (usually this.document).
*
* Needed only for print function.
* @type Object
*/
this.doc = null;
/**
* The name of an applet providing setImage function.
*
* Example of an applet providing setImage function:
* <PRE>
* -----------------------------------------------------------
*
* import java.applet.Applet;
* import java.awt.Graphics;
* import java.awt.Image;
* import java.awt.ruponent;
* import java.lang.Integer;
* import java.util.StringTokenizer;
*
* public class ImageOutput extends Applet {
* Image JSImage = null;
*
* public void init() {
* // some initialisation here
* }
*
* public void paint(Graphics g) {
* if (this.JSImage != null) {
* g.drawImage(this.JSImage, 0, 0, this);
* }
* }
*
* public void setImage(int w, int h, String pixels) {
* int pix[] = new int[w * h];
* StringTokenizer st = new StringTokenizer(pixels," ");
* int index = 0;
* while (st.hasMoreTokens()) {
* pix[index++]=Integer.parseInt(st.nextToken());
* }
* this.JSImage = createImage(new java.awt.image.MemoryImageSource(w, h, pix, 0, w));
* repaint();
* }
* }
*
* -----------------------------------------------------------
* </PRE>
* Javascript is used to passed a String of the image bytes separated by space. Array would
* be a better choice, but it seems that MS IE fails to pass JavaScript Array to Java correctly.
*
* Needed only for print function.
* @type String
*/
this.appletName = null;
/**
* Alpha chanel value.
* @type Integer
*/
this.alpha = 255;
}
GJavaOutput.prototype = new GOutput();
/**
* Java output printing parametrs setup function (needed only for print functions).
*
* @tparam Object doc Document object (usually this.document).
* @tparam String appletName Reciving java applet name.
*/
GJavaOutput.prototype.setup = function(doc,appletName) {
this.doc=doc;
this.appletName=appletName;
};
/**
* Returns the image canvas string to be passed to Java.
* @treturn String String representing the bytes of the image separated by spaces;
* @tparam GCanvas gcanvas 2D image canvas.
*/
GJavaOutput.prototype.get = function(gcanvas) {
time_now = new Date();
pixels = new String("");
for (y=0;y<gcanvas.height;y++) {
for (i=0;i<this.scale;i++) {
for (x=0;x<gcanvas.width;x++) {
for (j=0;j<this.scale;j++) {
pixels += (pixels.length>0?" ":"") + ((this.alpha << 24) | gcanvas.getcolor(x,y));
}
}
}
}
time_finish = new Date();
this.generation_time = time_finish - time_now;
delete time_now;
delete time_finish;
return pixels;
};
/**
* Passes the image canvas String to a given applet.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GJavaOutput.prototype.print = function(gcanvas) {
if ((this.doc != null) && (this.appletName != null)) {
this.doc.applets[this.appletName].setImage(gcanvas.width*this.scale,gcanvas.height*this.scale,this.get(gcanvas));
}
};
</script>
<script type="text/javascript">
<!--
// Usage examples
var white = Color.htmltoint("#FFFFFF");
var black = Color.htmltoint("#000000");
var gc_eyes = new GCanvas(30,30,white,1);
var output = new GHTMLOutput();
// eyes
var eyes_border_color = Color.htmltoint("#000000");
var eyes_color = Color.htmltoint("#D1DFF2");
var pupil_border_color = Color.htmltoint("#AA0000");
var pupil_color = Color.htmltoint("#FF0000");
var left_eye_center_x = Math.round(gc_eyes.width/4.) - 2;
var left_eye_center_y = Math.round(gc_eyes.height/2.);
var right_eye_center_x = left_eye_center_x*3;
var right_eye_center_y = left_eye_center_y;
var eye_a = left_eye_center_x - 1;
var eye_b = left_eye_center_x;
function eyes_handlerIE() {
Xpos = window.event.x + document.body.scrollLeft;
Ypos = window.event.y + document.body.scrollTop;
eyes(Xpos,Ypos);
}
function eyes_handlerMOZ(event) {
Xpos = event.clientX + window.pageXOffset;
Ypos = event.clientY + window.pageYOffset;
eyes(Xpos,Ypos);
}
function eyes(Xpos,Ypos) {
gc_eyes.clear();
if (document.all) {
eyeslayer = document.all["eyes"];
} else if (document.getElementById) {
eyeslayer = document.getElementById("eyes");
}
gc_eyes.ellipse(left_eye_center_x,left_eye_center_y,eye_a,eye_b,eyes_border_color);
gc_eyes.fill(left_eye_center_x,left_eye_center_y,eyes_color);
gc_eyes.ellipse(right_eye_center_x,right_eye_center_y,eye_a,eye_b,eyes_border_color);
gc_eyes.fill(right_eye_center_x,right_eye_center_y,eyes_color);
dx = Xpos - eyeslayer.offsetLeft+left_eye_center_x;
dy = Ypos - eyeslayer.offsetTop+left_eye_center_y;
angle = Math.atan2(dy,dx);
x = Math.round(eye_a*0.5*Math.cos(angle)+left_eye_center_x);
y = Math.round(eye_a*0.5*Math.sin(angle)+left_eye_center_y);
gc_eyes.circle(x,y,2,pupil_border_color);
gc_eyes.fill(x,y,pupil_color);
dx = Xpos - eyeslayer.offsetLeft+right_eye_center_x;
dy = Ypos - eyeslayer.offsetTop+right_eye_center_y;
angle = Math.atan2(dy,dx);
x = Math.round(eye_a*0.5*Math.cos(angle)+right_eye_center_x);
y = Math.round(eye_a*0.5*Math.sin(angle)+right_eye_center_y);
gc_eyes.circle(x,y,2,pupil_border_color);
gc_eyes.fill(x,y,pupil_color);
output.setup(this.document,"eyes");
output.print(gc_eyes);
}
// -->
</script>
</head>
<body onLoad="eyes(0,0); return true;">
<h1>Eyes</h1>
<p>These eyes look at your mouse pointer (once you have started them). They are rendered in HTML.
<form>
Click <input type="button" value="Start!" name="start_eyes" class="active" onClick="this.form.start_eyes.className="passive"; this.form.stop_eyes.className="active"; if (document.all) { document.onmousemove = eyes_handlerIE; } else if (document.getElementById) { window.addEventListener("mousemove",eyes_handlerMOZ,true); }"> to start eyes.
Click <input type="button" value="Stop!" name="stop_eyes" class="passive" onClick="this.form.start_eyes.className="active"; this.form.stop_eyes.className="passive"; if (document.all) { document.onmousemove = null; } else if (document.getElementById) { window.removeEventListener("mousemove",eyes_handlerMOZ,true); }"> to stop eyes.
<center><div id="eyes" style="position:relative;top:0;left:0;height:30;width:30;">[eyes]</div></center>
</form>
<p><div align="right"><a href="./ontop.html">Next</a> || <a href="./lines.html">Previous</a> || <a href="../html/index.html#examples">Index</a></div>
</body>
</html>
JavaScript Graphics 3
<html>
<head>
<title>JavaScriptGraphics</title>
<style type="text/css">
<!--
input.active {
border-width: 1;
border-style: solid;
border-color: #000000;
}
input.passive {
color: #C0C0C0;
border-width: 0;
border-style: solid;
border-color: #000000;
}
-->
</style>
<script type="text/javascript">
/*
JavaScriptGraphics v 0.6
Pixel graphics in Javascript.
Copyright (C) 2003 Kitya Karlson http://www.karlson.ru/, karlson@karlson.ru
Tested in Microsoft Internet Explorer 6 and Mozilla 1.3.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation in version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*! \mainpage JavaScriptGraphics
*
* \section intro Introduction
* <p>Color image is just a 2D array of colors. If you think about image this way you can see
* that it is possible to draw an image of the size N*M in HTML-only way - as a table with
* N columns and M rows, where each cell takes one pixel and has a background color assigned
* to it. Unfortunately even a small image represented like this in HTML results in a large
* and complex code for the browser. But for artifitial images it is very easy to use RLE
* compression - if there are several cells in a line of the same color you can
* replace them by one cell with the correct colspan/rowspan attributes assigned for it.
* <p>There are three cool things about this type of images:
* <ol>
* <li>They can be posted on the pages where images are not allowed (like some forums, or
* livejournal),
* <li>The size of HTML sended from the web server to client"s computer is not very large -
* the HTML for the images is generated on the client"s computer only,
* <li>They can be animated to react on user input.
* </ol>
* <p>I made a simple JavaScript library that allows you to use simple 2D graphics functions
* to create such images (like drawing lines, points or circles). Comments and suggestions are <a href="mailto:karlson@karlson.ru">welcome</a>!
* <p>As an alternative output method a handling of output to a Java applet is also provided in
* addition to a plain HTML rendering.
* <p>This library was tested in Microsoft Internet Explorer 6 and Mozilla 1.3.
*
* \section examples Examples
* <ul>
* <li><a href="../tests/clock.html">Analog Clock</a> - shows the difference between different types
* of HTML rendering
* <li><a href="../tests/lines.html">Color Sun</a> - shows an example of zooming in HTML output (Java output zooming is working in the same way).
* <li><a href="../tests/eyes.html">Eyes</a> - eyes that follow your mouse pointer.
* <li><a href="../tests/ontop.html">On Top</a> - DHTML output overlay example.
* <li><a href="../tests/plot.html">Sin/Cos Plot</a> - shows an example of HTML rendering and Java Applet processing (works only in Mozilla or in IE with Java virtual machine from SUN).
* </ul>
*
* \section conv Converter
* <a href="../tests/makeimage.php">Image to HTML converter</a> - this converter contains a preprocessing step, which is made using PHP and GD. Color dithering is produce to reduce the output complexity.
*
* \section changes Changes
* <p><b>v 0.6</b>
* <ul>
* <li>Image to HTML image converter added.
* </ul>
* <p><b>v 0.5</b>
* <ul>
* <li>Polygon and polyline drawing functions are added.
* <li>DHTML output option and overlay output options + invisible color are added to HTML output processor.
* <li>New example ("On Top") demonstrating new DHTML output options is added.
* </ul>
* <p><b>v 0.4</b>
* <ul>
* <li>Java Applet output methods are introduced in addition to HTML output methods.
* <li>Color values are now accepted in several formats.
* <li>Rendering time is calculated now.
* <li>Examples are updated to reflect new features.
* <li>A lot of bugfixes.
* </ul>
* <p><b>v 0.3</b>
* <ul>
* <li>Small bugfixes.
* <li>HTML output processor is moved to the separate class.
* <li>An optimised method of output compression - Optimised RLE - is introduced. It is using
* both colspan and rowspan attributes, dividing the table into the minimum number of cells.
* It is not so fast as the fast simple RLE, but it makes the tables really small. Which method
* is used for compression (Fast RLE or Optimised RLE) is controlled by the compression
* parametr of the HTML output class.
* </ul>
* <p><b>v 0.2</b>
* <ul>
* <li>Functions are rewritten as a class and moved to the separate file.
* <li>Area fill function is rewritten using stack instead of recursion - this allows large closed areas to be filled-in.
* <li>Code is cleaned up and documented using Doxygen.
* </ul>
* <p><b>v 0.1</b>
* <ul>
* <li>Initial release.
* </ul>
*
* \section downloads Downloads
* <ul>
* <li>Download <a href="../jsgraphics.0.6.zip">JavaScriptGraphics v 0.6</a> - latest.
* <li>Download <a href="../jsgraphics.0.5.zip">JavaScriptGraphics v 0.5</a>.
* <li>Download <a href="../jsgraphics.0.4.zip">JavaScriptGraphics v 0.4</a>.
* <li>Download <a href="../jsgraphics.0.3.zip">JavaScriptGraphics v 0.3</a>.
* <li>Download <a href="../jsgraphics.0.2.zip">JavaScriptGraphics v 0.2</a>.
* <li>Download <a href="../jsgraphics.0.1.zip">JavaScriptGraphics v 0.1</a>.
* </ul>
*
* \section legal Legal
* <p>This is <b>JavaScriptGraphics</b> library written in 2003 by Kitya Karlson <a href="mailto:karlson@karlson.ru">karlson@karlson.ru</a>.
* This software is distributed under <a href="http://www.gnu.org/licenses/lgpl.html">LGPL</a>.
*
* \section warning Warning
* <p>After working on this project for a couple of weeks I have found out that <a href="http://www.walterzorn.ru/jsgraphics/jsgraphics_e.htm">a simillar
* attempt</a> was made before already. The main differnce with my approach and the approach
* taken by Walter Zorn is that my method performs drawing on offscreen first (on array)
* and then creates optimised html only when flushed. Also in my method three types of
* output are supported (HTML table, DHTML and Java Applet) and not only one output method like
* in Walter"s class. So my method would work faster and provide better output for more complex
* images and is more suitable for animation, however Walter"s method works faster if you
* are in need of just one line.
*
*/
/**
* @file
* JavaScriptGraphics is a library for producing graphics using JavaScript
* by manipulating HTML tables. It uses "run length encoding" by taking
* advantage of colspan attributes in order to reduce the complexity of
* the output. Images created in this manner can be posted on the pages
* such as forums or LiveJournal where images are not allowed, and can
* be animated using JavaScript.
* The methods provided allow to draw lines, point, circles, ellipsoids and other
* geometrical figures.
*/
/**
* JSColor class provides functions for converting different color repersentations
* (HTML, RGB, INT) into each other. All methods of this class could be used as "static".
*
* Examples:
*
* HTML: #000000 - black, #FFFFFF - white,
*
* RGB: 0,0,0 - black, 255,255,255 - white,
*
* INT: 0 - black, 16777215 - white.
*
* @ctor
* Constructs JSColor class (empty).
*/
function JSColor() {
};
/**
* Converts RGB color to HTML color.
* @tparam Integer red Red component of the color.
* @tparam Integer green Green component of the color.
* @tparam Integer blue Blue component of the color.
* @treturn String HTML color.
*/
JSColor.prototype.rgbtohtml = function (red,green,blue) {
x="0123456789ABCDEF";
return "#" + x.charAt(red >> 4)+x.charAt(red & 15) + x.charAt(green >> 4)+x.charAt(green & 15) + x.charAt(blue >> 4) + x.charAt(blue & 15);
};
/**
* Converts INT color to HTML color.
* @tparam Integer rgb Color value.
* @treturn String HTML color.
*/
JSColor.prototype.inttohtml = function(rgb) {
return this.rgbtohtml( ((rgb >> 16) & 0xff), ((rgb >> 8) & 0xff ), (rgb & 0xff) );
};
/**
* Converts HTML color to INT color.
* @tparam String html HTML color.
* @treturn Integer Color value.
*/
JSColor.prototype.htmltoint = function(html) {
x="0123456789ABCDEF";
html = html.toUpperCase();
red = 16*x.indexOf(html.charAt(1))+x.indexOf(html.charAt(2));
green = 16*x.indexOf(html.charAt(3))+x.indexOf(html.charAt(4));
blue = 16*x.indexOf(html.charAt(5))+x.indexOf(html.charAt(6));
return (red << 16) | (green << 8) | blue;
};
/**
* Converts RGB color to INT color.
* @tparam Integer red Red component of the color.
* @tparam Integer green Green component of the color.
* @tparam Integer blue Blue component of the color.
* @treturn Integer Color value.
*/
JSColor.prototype.rgbtoint = function(red,green,blue) {
return (red << 16) | (green << 8) | blue;
};
/**
* "Static" Color object.
* @type JSColor
*/
var Color = new JSColor();
/**
* Simple 2D graphics canvas.
*
* x=0,y=0 - top left corner of the canvas.
* x=width-1,y=height-1 - bottom right corner of the canvas.
*
* @ctor
* Constructs a 2D image drawing canvas.
* @tparam Integer width The width of the canvas.
* @tparam Integer height The height of the canvas.
* @tparam Integer bgcolor The background color of the canvas.
*/
function GCanvas(width, height, bgcolor) {
/**
* The width of the canvas.
* @type Integer
*/
this.width=((width>0)?width:0) || 35;
/**
* The height of the canvas.
* @type Integer
*/
this.height=((height>0)?height:0) || 35;
/**
* The background color of the canvas (HTML format string).
* @type String
*/
this.bgcolor=bgcolor || 0;
/**
* Internal array representing the image canvas.
* @type Array
*/
this.image = new Array(this.height*this.width);
for (i=0;i<this.height*this.width;i++) {
this.image[i]=this.bgcolor;
}
};
/**
* Clears the whole canvas using default background color.
*/
GCanvas.prototype.clear = function() {
for (i=0; i < this.height*this.width; i++) {
this.image[i]=this.bgcolor;
}
};
/**
* Puts a pixel of the defined color in the position x,y.
* @tparam Integer x X coordinate of the pixel.
* @tparam Integer y Y coordinate of the pixel.
* @tparam Integer color The color of the pixel.
*/
GCanvas.prototype.draw = function(x,y,color) {
if ((x >= 0) && (y >= 0) && (y < this.height) && (x < this.width)) {
this.image[y*this.width+x]=color;
}
};
/**
* Gets a color of a pixel in the position x,y
* @treturn Integer Color of the pixel.
*/
GCanvas.prototype.getcolor = function(x,y) {
if ((x >= 0) && (y >= 0) && (y < this.height) && (x < this.width)) {
return this.image[y*this.width+x];
} else {
return null;
}
};
/**
* Draws a line (Bresenham"s algorithm).
* @tparam Integer x1 X coordinate of the start pixel.
* @tparam Integer y1 Y coordinate of the start pixel.
* @tparam Integer x2 X coordinate of the ending pixel.
* @tparam Integer y2 Y coordinate of the ending pixel.
* @tparam Integer color The color of the line.
*/
GCanvas.prototype.line = function(x1, y1, x2, y2, color)
{
var pX=(x1<x2) ? 1 : -1;
var pY=(y1<y2) ? 1 : -1;
var E;
var Delta1;
var Delta2;
var X=x1;
var Y=y1;
var I=1;
var temp;
if (x1>x2) { temp = x1; x1=x2; x2=temp; }
if (y1>y2) { temp = y1; y1=y2; y2=temp; }
var dX=x2-x1;
var dY=y2-y1;
this.draw(X, Y, color);
if (dX>=dY)
{
Delta1=dY<<1;
Delta2=(dY-dX)<<1;
E=Delta1-dX;
for (X+=pX; I<=dX; I++, X+=pX)
{
if (E>0)
{
E+=Delta2;
Y+=pY;
}
else E+=Delta1;
this.draw(X, Y, color);
}
}
else
{
Delta1=dX<<1;
Delta2=(dX-dY)<<1;
E=Delta1-dY;
for (Y+=pY; I<=dY; I++, Y+=pY)
{
if (E>0)
{
E+=Delta2;
X+=pX;
}
else E+=Delta1;
this.draw(X,Y,color);
}
}
};
/**
* Draws a circle (Bresenham"s algorithm).
* @tparam Integer xc X coordinate of the center of the circle.
* @tparam Integer yc Y coordinate of the center of the circle.
* @tparam Integer r The radius of the circle.
* @tparam Integer color The color of the circle.
*/
GCanvas.prototype.circle = function(xc,yc,r,color) {
var y = r;
var x = 0;
var d = 3 - 2*r;
while (x <= y) {
this.draw(x+xc,y+yc,color);
this.draw(x+xc,-y+yc,color);
this.draw(-x+xc,-y+yc,color);
this.draw(-x+xc,y+yc,color);
this.draw(y+xc,x+yc,color);
this.draw(y+xc,-x+yc,color);
this.draw(-y+xc,-x+yc,color);
this.draw(-y+xc,x+yc,color);
if (d < 0) {
d = d + 4*x +6;
} else {
d = d + 4*(x-y) + 10;
y = y-1;
}
x = x+1;
}
};
/**
* Draws an ellipse (Bresenham"s algorithm).
* @tparam Integer xc X coordinate of the center of the circle.
* @tparam Integer yc Y coordinate of the center of the circle.
* @tparam Integer a The semi-axis of the ellipse.
* @tparam Integer b The semi-axis of the ellipse.
* @tparam Integer color The color of the ellipse.
*/
GCanvas.prototype.ellipse = function(xc,yc,a,b,color)
{
b_square=b*b;
a_square=a*a;
row=b;
col=0;
two_a_square=a_square<<1;
four_a_square=a_square<<2;
four_b_square=b_square<<2;
two_b_square=b_square<<1;
d=two_a_square*((row-1)*(row))+a_square+two_b_square*(1-a_square);
while(a_square*(row)>b_square*(col))
{
this.draw(col+xc, row+yc, color);
this.draw(col+xc, yc-row, color);
this.draw(xc-col, row+yc, color);
this.draw(xc-col, yc-row, color);
if (d>=0)
{
row--;
d-=four_a_square*(row);
}
d+=two_b_square*(3+(col<<1));
col++;
}
d=two_b_square*(col+1)*col+two_a_square*(row*(row-2)+1)+(1-two_a_square)*b_square;
while ((row) + 1)
{
this.draw(col+xc, row+yc, color);
this.draw(col+xc, yc-row, color);
this.draw(xc-col, row+yc, color);
this.draw(xc-col, yc-row, color);
if (d<=0)
{
col++;
d+=four_b_square*col;
}
row--;
d+=two_a_square*(3-(row <<1));
}
};
/**
* Fills a closed area (using stack)
* @tparam Integer x X coordinate of the point inside the area to be filled-in.
* @tparam Integer y Y coordinate of the point inside the area to be filled-in.
* @tparam Integer color Fill color.
*/
GCanvas.prototype.fill = function(x,y,color) {
stack_head=0;
stack_tail=0;
floodfill_stackx = new Array((this.width+2)*(this.height+2));
floodfill_stacky = new Array((this.width+2)*(this.height+2));
clr=this.getcolor(x,y);
floodfill_stackx[stack_head]=x;
floodfill_stacky[stack_head]=y;
this.draw(x,y,color);
stack_head++;
while ( (stack_head<((this.width+2)*(this.height+2))) && (stack_head>stack_tail) ) {
x=floodfill_stackx[stack_tail];
y=floodfill_stacky[stack_tail];
stack_tail++;
if (x>=0 && y>=0 && x<this.width && y<this.height) {
if (this.getcolor(x+1,y)==clr) {
floodfill_stackx[stack_head]=x+1;
floodfill_stacky[stack_head]=y;
this.draw(x+1,y,color);
stack_head++;
}
if (this.getcolor(x-1,y)==clr) {
floodfill_stackx[stack_head]=x-1;
floodfill_stacky[stack_head]=y;
this.draw(x-1,y,color);
stack_head++;
}
if (this.getcolor(x,y+1)==clr) {
floodfill_stackx[stack_head]=x;
floodfill_stacky[stack_head]=y+1;
this.draw(x,y+1,color);
stack_head++;
}
if (this.getcolor(x,y-1)==clr) {
floodfill_stackx[stack_head]=x;
floodfill_stacky[stack_head]=y-1;
this.draw(x,y-1,color);
stack_head++;
}
}
}
delete floodfill_stacky;
delete floodfill_stackx;
};
/**
* Draws a polyline.
* @tparam Array x x1,x2, ..., xn.
* @tparam Array y y1,y2, ..., yn.
* @tparam Integer color Polyline color.
*/
GCanvas.prototype.polyline = function(x, y, color) {
var z = x.length-1; while (z >= 0) this.line(x[z], y[z], x[--z], y[z], color);
};
/**
* Draws a polygon (automatically closed if last points are not identical.
* @tparam Array x x1,x2, ..., xn.
* @tparam Array y y1,y2, ..., yn.
* @tparam Integer color Polygon color.
*/
GCanvas.prototype.polygon = function(x, y, color) {
this.polyline(x, y, color);
this.line(x[x.length-1], y[x.length-1], x[0], y[0], color);
};
/**
* Output processor.
*
* An abstract output processor.
*
* @ctor
* Abstract output processor.
* @tparam Integer scale The scaling of the output (1 = 1x = no scaling).
*/
function GOutput(scale) {
/**
* Scaling of the output (1 = 1x = no scaling).
* @type Integer
*/
this.scale=scale || 1;
}
/**
* HTML output processor.
*
* This output processor can be used to render the canvas as an HTML table.
* Two types ("Fast RLE" and "Optimised RLE") of output rendering are provided,
* see bellow.
*
* @ctor
* Constructs an HTML output processor.
*/
function GHTMLOutput() {
/**
* Compression parametr (0 - fast RLE, 1 - optimised RLE).
* @type Integer
*/
this.rupression=0;
/**
* Output type - HTML (table) or DHTML (div"s). If dhtml is set to false HTML output
* is produced and if dhtml is set to true DHTML output is produced.
* @type Boolean
*/
this.dhtml = true;
/**
* An invsibile color. By default invisible_color = -1, i.e. the default background of the canvas.
* @type Integer
*/
this.invisible_color = -1;
/**
* Number of cells generated in the HTML table.
* @type Integer
*/
this.number_of_cells=0;
/**
* Time (in ms.) used for the generation of the HTML table.
* @type Integer
*/
this.generation_time=0;
/**
* Javascript document object (usually this.document).
*
* Needed only for print functions.
* @type Object
*/
this.doc = null;
/**
* Output layer ID.
*
* Needed only for print functions.
* @type String
*/
this.layerId = null;
/**
* Append or overwrite the layer.
*
* Needed only for print functions.
* @type Boolean
*/
this.append = false;
}
GHTMLOutput.prototype = new GOutput();
/**
* HTML output printing parametrs setup function.
*
* @tparam Object doc Document object (usually this.document).
* @tparam String layerId Output layer ID.
*/
GHTMLOutput.prototype.setup = function(doc,layerId) {
this.doc=doc;
this.layerId=layerId;
};
/**
* Returns the image canvas html (using RLE compression on lines = fast RLE).
* @treturn String A table in HTML format representing the image canvas.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.get_html = function(gcanvas) {
time_now = new Date();
this.number_of_cells = 0;
if (this.invisible_color == -1) {
inv_color = gcanvas.bgcolor;
} else {
inv_color = this.invisibile_color;
}
str = new String("");
len = 0;
if (! this.dhtml) {
str += "<table border=0 cellspacing=0 cellpadding=0 width="+gcanvas.width*this.scale+" height="+gcanvas.height*this.scale+">";
}
for (i=0; i < gcanvas.height; i++) {
if (! this.dhtml) {
str += "<tr height="+this.scale+" width="+gcanvas.width*this.scale+">";
}
current_color = gcanvas.getcolor(0,i);
len = 0;
start_j = 0;
for (j=0; j < gcanvas.width; j++) {
if ( (gcanvas.getcolor(j,i) != current_color) || (j == gcanvas.width-1)) {
if (j== gcanvas.width-1) { len++; }
if (! this.dhtml) {
str += "<td width="+this.scale*len+" height="+this.scale + ( (len>1) ? " colspan="+len : "" ) + ( (current_color!=inv_color) ? " bgcolor="+Color.inttohtml(current_color) : "") + "></td>";
} else {
if (current_color!=inv_color) {
str += "<div style="position:absolute;"+ "left:" + (start_j*this.scale) + "px;"+ "top:" + (i*this.scale) + "px;"+ "width:" + (this.scale*len) + "px;"+ "height:" + this.scale + "px;"+ "clip:rect(0,"+(this.scale*len)+"px,"+this.scale+"px,0);" + "overflow:hidden;background-color:" + Color.inttohtml(current_color) + ";" + ""><\/div>";
}
}
this.number_of_cells++;
len=1;
start_j = j;
current_color=gcanvas.getcolor(j,i);
} else {
len++;
}
}
if (! this.dhtml) {
str += "</tr>";
}
}
if (! this.dhtml) {
str += "</table>";
}
time_finish = new Date();
this.generation_time = time_finish - time_now;
delete time_now;
delete time_finish;
return str;
};
/**
* Returns the image canvas html (using RLE compression on both lines and rows = optimised RLE).
* @treturn String A table in HTML format representing the image canvas.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.get_optimised_html = function(gcanvas) {
time_now = new Date();
if (this.invisible_color == -1) {
inv_color = gcanvas.bgcolor;
} else {
inv_color = this.invisibile_color;
}
this.number_of_cells = 0;
str = new String("");
flushed = new Array(gcanvas.height*gcanvas.width);
for (i=0;i<gcanvas.height*gcanvas.width;i++) {
flushed[i]=0;
}
if (! this.dhtml) {
str += "<table border=0 cellspacing=0 cellpadding=0 width="+gcanvas.width*this.scale+" height="+gcanvas.height*this.scale+">";
}
for (i=0; i < gcanvas.height; i++) {
if (! this.dhtml) {
str += "<tr height="+this.scale+" width="+gcanvas.width*this.scale+">";
}
for (j=0; j < gcanvas.width; j++) {
if (flushed[i*gcanvas.width+j] == 0) {
current_color = gcanvas.getcolor(j,i);
k=gcanvas.height;
opt = 0;
colspan = 1;
rowspan = 1;
for (x=j; x < gcanvas.width; x++) {
if (flushed[i*gcanvas.height+x]==1) { break; }
if (gcanvas.getcolor(x,i) != current_color) { break; }
for (y=i; y < k; y++) {
if (flushed[y*gcanvas.width+x]==1) { break; }
if (gcanvas.getcolor(x,y) != current_color) { break; }
}
if (y-1<0) { break; }
if (gcanvas.getcolor(x,y-1) != current_color) { break; }
k=y;
if ( ((x-j+1)*(y-i)) > opt) {
opt=(x-j+1)*(y-i);
colspan = x-j+1;
rowspan = y-i;
}
}
for (y=i; y < i+rowspan; y++) {
for (x=j; x < j+colspan; x++) {
flushed[y*gcanvas.width+x]=1;
}
}
if (! this.dhtml) {
str += "<td width="+this.scale*colspan+" height="+ this.scale*rowspan + ( (colspan>1) ? " colspan="+colspan : "" ) + ( (rowspan > 1) ? " rowspan=" + rowspan : "") + ( (current_color!=inv_color) ? " bgcolor="+Color.inttohtml(current_color) : "") + "></td>";
} else {
if (current_color!=inv_color) {
str += "<div style="position:absolute;"+ "left:" + (j*this.scale) + "px;"+ "top:" + (i*this.scale) + "px;"+ "width:" + (this.scale*colspan) + "px;"+ "height:" + (this.scale*rowspan) + "px;"+ "clip:rect(0,"+(this.scale*colspan)+"px,"+(this.scale*rowspan)+"px,0);" + "overflow:hidden;background-color:" + Color.inttohtml(current_color) + ";" + ""><\/div>";
}
}
this.number_of_cells++;
}
}
if (! this.dhtml) {
str += "</tr>";
}
}
if (! this.dhtml) {
str += "</table>";
}
delete flushed;
time_finish = new Date();
this.generation_time = time_finish - time_now;
delete time_now;
delete time_finish;
return str;
};
/**
* Assigns the image canvas html (using RLE compression on lines = fast RLE) to a given layer.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.print_html = function(gcanvas) {
if ((this.doc != null) && (this.layerId != null)) {
if (this.doc.all) {
outlayer = this.doc.all[this.layerId];
}
if (this.doc.getElementById) {
outlayer = this.doc.getElementById(this.layerId);
}
if (this.append) {
outlayer.innerHTML += this.get_html(gcanvas);
} else {
outlayer.innerHTML = this.get_html(gcanvas);
}
}
};
/**
* Assigns the image canvas html (using RLE compression on both lines and rows = optimised RLE) to a given layer.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.print_optimised_html = function(gcanvas) {
if ((this.doc != null) && (this.layerId != null)) {
if (this.doc.all) {
outlayer = this.doc.all[this.layerId];
}
if (this.doc.getElementById) {
outlayer = this.doc.getElementById(this.layerId);
}
if (this.append) {
outlayer.innerHTML += this.get_optimised_html(gcanvas);
} else {
outlayer.innerHTML = this.get_optimised_html(gcanvas);
}
}
};
/**
* Returns the image canvas html
* @treturn String A table in HTML format representing the image canvas.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.get = function(gcanvas) {
switch (this.rupression) {
case 0: return this.get_html(gcanvas); break;
case 1: return this.get_optimised_html(gcanvas); break;
default: return this.get_html(gcanvas); break;
}
};
/**
* Assigns the image canvas html to a given layer.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.print = function(gcanvas) {
switch (this.rupression) {
case 0: return this.print_html(gcanvas); break;
case 1: return this.print_optimised_html(gcanvas); break;
default: return this.print_html(gcanvas); break;
}
}
/**
* Java (applet) output processor.
*
* This output processor can be used to pass your canvas to a Java applet for rendering.
*
* @ctor
* Constructs an applet output processor.
*/
function GJavaOutput() {
/**
* Time (in ms.) used for the generation of the image string to be passed to the applet.
* @type Integer
*/
this.generation_time=0;
/**
* Javascript document object (usually this.document).
*
* Needed only for print function.
* @type Object
*/
this.doc = null;
/**
* The name of an applet providing setImage function.
*
* Example of an applet providing setImage function:
* <PRE>
* -----------------------------------------------------------
*
* import java.applet.Applet;
* import java.awt.Graphics;
* import java.awt.Image;
* import java.awt.ruponent;
* import java.lang.Integer;
* import java.util.StringTokenizer;
*
* public class ImageOutput extends Applet {
* Image JSImage = null;
*
* public void init() {
* // some initialisation here
* }
*
* public void paint(Graphics g) {
* if (this.JSImage != null) {
* g.drawImage(this.JSImage, 0, 0, this);
* }
* }
*
* public void setImage(int w, int h, String pixels) {
* int pix[] = new int[w * h];
* StringTokenizer st = new StringTokenizer(pixels," ");
* int index = 0;
* while (st.hasMoreTokens()) {
* pix[index++]=Integer.parseInt(st.nextToken());
* }
* this.JSImage = createImage(new java.awt.image.MemoryImageSource(w, h, pix, 0, w));
* repaint();
* }
* }
*
* -----------------------------------------------------------
* </PRE>
* Javascript is used to passed a String of the image bytes separated by space. Array would
* be a better choice, but it seems that MS IE fails to pass JavaScript Array to Java correctly.
*
* Needed only for print function.
* @type String
*/
this.appletName = null;
/**
* Alpha chanel value.
* @type Integer
*/
this.alpha = 255;
}
GJavaOutput.prototype = new GOutput();
/**
* Java output printing parametrs setup function (needed only for print functions).
*
* @tparam Object doc Document object (usually this.document).
* @tparam String appletName Reciving java applet name.
*/
GJavaOutput.prototype.setup = function(doc,appletName) {
this.doc=doc;
this.appletName=appletName;
};
/**
* Returns the image canvas string to be passed to Java.
* @treturn String String representing the bytes of the image separated by spaces;
* @tparam GCanvas gcanvas 2D image canvas.
*/
GJavaOutput.prototype.get = function(gcanvas) {
time_now = new Date();
pixels = new String("");
for (y=0;y<gcanvas.height;y++) {
for (i=0;i<this.scale;i++) {
for (x=0;x<gcanvas.width;x++) {
for (j=0;j<this.scale;j++) {
pixels += (pixels.length>0?" ":"") + ((this.alpha << 24) | gcanvas.getcolor(x,y));
}
}
}
}
time_finish = new Date();
this.generation_time = time_finish - time_now;
delete time_now;
delete time_finish;
return pixels;
};
/**
* Passes the image canvas String to a given applet.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GJavaOutput.prototype.print = function(gcanvas) {
if ((this.doc != null) && (this.appletName != null)) {
this.doc.applets[this.appletName].setImage(gcanvas.width*this.scale,gcanvas.height*this.scale,this.get(gcanvas));
}
};
</script>
<script type="text/javascript">
<!--
// Usage examples
var white = Color.htmltoint("#FFFFFF");
var black = Color.htmltoint("#000000");
var gc = new GCanvas(35,35,white,1);
var output = new GHTMLOutput();
// color lines
var cenx = Math.round(gc.width/2.)-1;
var ceny = Math.round(gc.height/2.)-1;
var rad = Math.min(Math.round(gc.width/2.),Math.round(gc.height/2.))-3;
var colors = new Array("#000000", "#C0C0C0", "#808080", "#800000", "#FF0000", "#800080", "#FF00FF", "#008000", "#00FF00", "#808000", "#FFFF00", "#000080", "#0000FF", "#008080", "#00FFFF");
var lc = 0;
var lines_runs = 0;
var numlines = 11;
var yellow = Color.htmltoint("#F4ED21");
function lines() {
gc.clear();
gc.circle(cenx,ceny,6,yellow);
gc.fill(cenx,ceny,yellow);
for (s=0; s<numlines; s++) {
lc = (lc+1)%colors.length;
sr = (s/numlines)*2*Math.PI-.5*Math.PI;
sx = Math.round(rad*Math.cos(sr)+cenx);
sy = Math.round(rad*Math.sin(sr)+ceny);
sx1 = Math.round(7*Math.cos(sr)+cenx);
sy1 = Math.round(7*Math.sin(sr)+ceny);
gc.line(sx1,sy1,sx,sy, Color.htmltoint(colors[lc]));
}
output.setup(this.document,"lines");
output.scale=1;
output.print(gc);
output.setup(this.document,"scaled_lines");
output.scale=2;
output.print(gc);
if (lines_runs == 1) {
setTimeout("lines()", 255);
}
}
// -->
</script>
</head>
<body onLoad="lines();return true;">
<h1>Color Sun</h1>
<p>Both HTML and Java output renderers support scaling parametr. This simple example shows
the result of 2x scaling in HTML.
<form>
Click <input type="button" value="Start!" name="start_lines" class="active" onClick="if (lines_runs==0) { this.form.start_lines.className="passive"; this.form.stop_lines.className="active"; lines_runs=1; lines(); }"> to start drawing lines.
Click <input type="button" value="Stop!" name="stop_lines" class="passive" onClick="this.form.start_lines.className="active"; this.form.stop_lines.className="passive"; lines_runs=0;"> to stop drawing lines.
</form>
<table border="0" cellpadding=4 cellspacing=0 bgcolor="#ffffff" style="border: 1px dotted rgb(0,0,0)">
<tr height=35>
<th height=35>1x</td>
<th height=35>2x</div>
</tr>
<tr height=35>
<td height=35><div id="lines" style="position:relative;top:0;left:0;height:35;width:35;">[lines]</div></td>
<td height=70><div id="scaled_lines" style="position:relative;top:0;left:0;height:70;width:70;">[scaled_lines]</div></td>
</tr>
</table>
<p><div align="right"><a href="./eyes.html">Next</a> || <a href="./plot.html">Previous</a> || <a href="../html/index.html#examples">Index</a></div>
</body>
</html>
JavaScript Graphics 4
<html>
<head>
<title>JavaScriptGraphics</title>
<style type="text/css">
<!--
input.active {
border-width: 1;
border-style: solid;
border-color: #000000;
}
input.passive {
color: #C0C0C0;
border-width: 0;
border-style: solid;
border-color: #000000;
}
-->
</style>
<script type="text/javascript">
/*
JavaScriptGraphics v 0.6
Pixel graphics in Javascript.
Copyright (C) 2003 Kitya Karlson http://www.karlson.ru/, karlson@karlson.ru
Tested in Microsoft Internet Explorer 6 and Mozilla 1.3.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation in version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*! \mainpage JavaScriptGraphics
*
* \section intro Introduction
* <p>Color image is just a 2D array of colors. If you think about image this way you can see
* that it is possible to draw an image of the size N*M in HTML-only way - as a table with
* N columns and M rows, where each cell takes one pixel and has a background color assigned
* to it. Unfortunately even a small image represented like this in HTML results in a large
* and complex code for the browser. But for artifitial images it is very easy to use RLE
* compression - if there are several cells in a line of the same color you can
* replace them by one cell with the correct colspan/rowspan attributes assigned for it.
* <p>There are three cool things about this type of images:
* <ol>
* <li>They can be posted on the pages where images are not allowed (like some forums, or
* livejournal),
* <li>The size of HTML sended from the web server to client"s computer is not very large -
* the HTML for the images is generated on the client"s computer only,
* <li>They can be animated to react on user input.
* </ol>
* <p>I made a simple JavaScript library that allows you to use simple 2D graphics functions
* to create such images (like drawing lines, points or circles). Comments and suggestions are <a href="mailto:karlson@karlson.ru">welcome</a>!
* <p>As an alternative output method a handling of output to a Java applet is also provided in
* addition to a plain HTML rendering.
* <p>This library was tested in Microsoft Internet Explorer 6 and Mozilla 1.3.
*
* \section examples Examples
* <ul>
* <li><a href="../tests/clock.html">Analog Clock</a> - shows the difference between different types
* of HTML rendering
* <li><a href="../tests/lines.html">Color Sun</a> - shows an example of zooming in HTML output (Java output zooming is working in the same way).
* <li><a href="../tests/eyes.html">Eyes</a> - eyes that follow your mouse pointer.
* <li><a href="../tests/ontop.html">On Top</a> - DHTML output overlay example.
* <li><a href="../tests/plot.html">Sin/Cos Plot</a> - shows an example of HTML rendering and Java Applet processing (works only in Mozilla or in IE with Java virtual machine from SUN).
* </ul>
*
* \section conv Converter
* <a href="../tests/makeimage.php">Image to HTML converter</a> - this converter contains a preprocessing step, which is made using PHP and GD. Color dithering is produce to reduce the output complexity.
*
* \section changes Changes
* <p><b>v 0.6</b>
* <ul>
* <li>Image to HTML image converter added.
* </ul>
* <p><b>v 0.5</b>
* <ul>
* <li>Polygon and polyline drawing functions are added.
* <li>DHTML output option and overlay output options + invisible color are added to HTML output processor.
* <li>New example ("On Top") demonstrating new DHTML output options is added.
* </ul>
* <p><b>v 0.4</b>
* <ul>
* <li>Java Applet output methods are introduced in addition to HTML output methods.
* <li>Color values are now accepted in several formats.
* <li>Rendering time is calculated now.
* <li>Examples are updated to reflect new features.
* <li>A lot of bugfixes.
* </ul>
* <p><b>v 0.3</b>
* <ul>
* <li>Small bugfixes.
* <li>HTML output processor is moved to the separate class.
* <li>An optimised method of output compression - Optimised RLE - is introduced. It is using
* both colspan and rowspan attributes, dividing the table into the minimum number of cells.
* It is not so fast as the fast simple RLE, but it makes the tables really small. Which method
* is used for compression (Fast RLE or Optimised RLE) is controlled by the compression
* parametr of the HTML output class.
* </ul>
* <p><b>v 0.2</b>
* <ul>
* <li>Functions are rewritten as a class and moved to the separate file.
* <li>Area fill function is rewritten using stack instead of recursion - this allows large closed areas to be filled-in.
* <li>Code is cleaned up and documented using Doxygen.
* </ul>
* <p><b>v 0.1</b>
* <ul>
* <li>Initial release.
* </ul>
*
* \section downloads Downloads
* <ul>
* <li>Download <a href="../jsgraphics.0.6.zip">JavaScriptGraphics v 0.6</a> - latest.
* <li>Download <a href="../jsgraphics.0.5.zip">JavaScriptGraphics v 0.5</a>.
* <li>Download <a href="../jsgraphics.0.4.zip">JavaScriptGraphics v 0.4</a>.
* <li>Download <a href="../jsgraphics.0.3.zip">JavaScriptGraphics v 0.3</a>.
* <li>Download <a href="../jsgraphics.0.2.zip">JavaScriptGraphics v 0.2</a>.
* <li>Download <a href="../jsgraphics.0.1.zip">JavaScriptGraphics v 0.1</a>.
* </ul>
*
* \section legal Legal
* <p>This is <b>JavaScriptGraphics</b> library written in 2003 by Kitya Karlson <a href="mailto:karlson@karlson.ru">karlson@karlson.ru</a>.
* This software is distributed under <a href="http://www.gnu.org/licenses/lgpl.html">LGPL</a>.
*
* \section warning Warning
* <p>After working on this project for a couple of weeks I have found out that <a href="http://www.walterzorn.ru/jsgraphics/jsgraphics_e.htm">a simillar
* attempt</a> was made before already. The main differnce with my approach and the approach
* taken by Walter Zorn is that my method performs drawing on offscreen first (on array)
* and then creates optimised html only when flushed. Also in my method three types of
* output are supported (HTML table, DHTML and Java Applet) and not only one output method like
* in Walter"s class. So my method would work faster and provide better output for more complex
* images and is more suitable for animation, however Walter"s method works faster if you
* are in need of just one line.
*
*/
/**
* @file
* JavaScriptGraphics is a library for producing graphics using JavaScript
* by manipulating HTML tables. It uses "run length encoding" by taking
* advantage of colspan attributes in order to reduce the complexity of
* the output. Images created in this manner can be posted on the pages
* such as forums or LiveJournal where images are not allowed, and can
* be animated using JavaScript.
* The methods provided allow to draw lines, point, circles, ellipsoids and other
* geometrical figures.
*/
/**
* JSColor class provides functions for converting different color repersentations
* (HTML, RGB, INT) into each other. All methods of this class could be used as "static".
*
* Examples:
*
* HTML: #000000 - black, #FFFFFF - white,
*
* RGB: 0,0,0 - black, 255,255,255 - white,
*
* INT: 0 - black, 16777215 - white.
*
* @ctor
* Constructs JSColor class (empty).
*/
function JSColor() {
};
/**
* Converts RGB color to HTML color.
* @tparam Integer red Red component of the color.
* @tparam Integer green Green component of the color.
* @tparam Integer blue Blue component of the color.
* @treturn String HTML color.
*/
JSColor.prototype.rgbtohtml = function (red,green,blue) {
x="0123456789ABCDEF";
return "#" + x.charAt(red >> 4)+x.charAt(red & 15) + x.charAt(green >> 4)+x.charAt(green & 15) + x.charAt(blue >> 4) + x.charAt(blue & 15);
};
/**
* Converts INT color to HTML color.
* @tparam Integer rgb Color value.
* @treturn String HTML color.
*/
JSColor.prototype.inttohtml = function(rgb) {
return this.rgbtohtml( ((rgb >> 16) & 0xff), ((rgb >> 8) & 0xff ), (rgb & 0xff) );
};
/**
* Converts HTML color to INT color.
* @tparam String html HTML color.
* @treturn Integer Color value.
*/
JSColor.prototype.htmltoint = function(html) {
x="0123456789ABCDEF";
html = html.toUpperCase();
red = 16*x.indexOf(html.charAt(1))+x.indexOf(html.charAt(2));
green = 16*x.indexOf(html.charAt(3))+x.indexOf(html.charAt(4));
blue = 16*x.indexOf(html.charAt(5))+x.indexOf(html.charAt(6));
return (red << 16) | (green << 8) | blue;
};
/**
* Converts RGB color to INT color.
* @tparam Integer red Red component of the color.
* @tparam Integer green Green component of the color.
* @tparam Integer blue Blue component of the color.
* @treturn Integer Color value.
*/
JSColor.prototype.rgbtoint = function(red,green,blue) {
return (red << 16) | (green << 8) | blue;
};
/**
* "Static" Color object.
* @type JSColor
*/
var Color = new JSColor();
/**
* Simple 2D graphics canvas.
*
* x=0,y=0 - top left corner of the canvas.
* x=width-1,y=height-1 - bottom right corner of the canvas.
*
* @ctor
* Constructs a 2D image drawing canvas.
* @tparam Integer width The width of the canvas.
* @tparam Integer height The height of the canvas.
* @tparam Integer bgcolor The background color of the canvas.
*/
function GCanvas(width, height, bgcolor) {
/**
* The width of the canvas.
* @type Integer
*/
this.width=((width>0)?width:0) || 35;
/**
* The height of the canvas.
* @type Integer
*/
this.height=((height>0)?height:0) || 35;
/**
* The background color of the canvas (HTML format string).
* @type String
*/
this.bgcolor=bgcolor || 0;
/**
* Internal array representing the image canvas.
* @type Array
*/
this.image = new Array(this.height*this.width);
for (i=0;i<this.height*this.width;i++) {
this.image[i]=this.bgcolor;
}
};
/**
* Clears the whole canvas using default background color.
*/
GCanvas.prototype.clear = function() {
for (i=0; i < this.height*this.width; i++) {
this.image[i]=this.bgcolor;
}
};
/**
* Puts a pixel of the defined color in the position x,y.
* @tparam Integer x X coordinate of the pixel.
* @tparam Integer y Y coordinate of the pixel.
* @tparam Integer color The color of the pixel.
*/
GCanvas.prototype.draw = function(x,y,color) {
if ((x >= 0) && (y >= 0) && (y < this.height) && (x < this.width)) {
this.image[y*this.width+x]=color;
}
};
/**
* Gets a color of a pixel in the position x,y
* @treturn Integer Color of the pixel.
*/
GCanvas.prototype.getcolor = function(x,y) {
if ((x >= 0) && (y >= 0) && (y < this.height) && (x < this.width)) {
return this.image[y*this.width+x];
} else {
return null;
}
};
/**
* Draws a line (Bresenham"s algorithm).
* @tparam Integer x1 X coordinate of the start pixel.
* @tparam Integer y1 Y coordinate of the start pixel.
* @tparam Integer x2 X coordinate of the ending pixel.
* @tparam Integer y2 Y coordinate of the ending pixel.
* @tparam Integer color The color of the line.
*/
GCanvas.prototype.line = function(x1, y1, x2, y2, color)
{
var pX=(x1<x2) ? 1 : -1;
var pY=(y1<y2) ? 1 : -1;
var E;
var Delta1;
var Delta2;
var X=x1;
var Y=y1;
var I=1;
var temp;
if (x1>x2) { temp = x1; x1=x2; x2=temp; }
if (y1>y2) { temp = y1; y1=y2; y2=temp; }
var dX=x2-x1;
var dY=y2-y1;
this.draw(X, Y, color);
if (dX>=dY)
{
Delta1=dY<<1;
Delta2=(dY-dX)<<1;
E=Delta1-dX;
for (X+=pX; I<=dX; I++, X+=pX)
{
if (E>0)
{
E+=Delta2;
Y+=pY;
}
else E+=Delta1;
this.draw(X, Y, color);
}
}
else
{
Delta1=dX<<1;
Delta2=(dX-dY)<<1;
E=Delta1-dY;
for (Y+=pY; I<=dY; I++, Y+=pY)
{
if (E>0)
{
E+=Delta2;
X+=pX;
}
else E+=Delta1;
this.draw(X,Y,color);
}
}
};
/**
* Draws a circle (Bresenham"s algorithm).
* @tparam Integer xc X coordinate of the center of the circle.
* @tparam Integer yc Y coordinate of the center of the circle.
* @tparam Integer r The radius of the circle.
* @tparam Integer color The color of the circle.
*/
GCanvas.prototype.circle = function(xc,yc,r,color) {
var y = r;
var x = 0;
var d = 3 - 2*r;
while (x <= y) {
this.draw(x+xc,y+yc,color);
this.draw(x+xc,-y+yc,color);
this.draw(-x+xc,-y+yc,color);
this.draw(-x+xc,y+yc,color);
this.draw(y+xc,x+yc,color);
this.draw(y+xc,-x+yc,color);
this.draw(-y+xc,-x+yc,color);
this.draw(-y+xc,x+yc,color);
if (d < 0) {
d = d + 4*x +6;
} else {
d = d + 4*(x-y) + 10;
y = y-1;
}
x = x+1;
}
};
/**
* Draws an ellipse (Bresenham"s algorithm).
* @tparam Integer xc X coordinate of the center of the circle.
* @tparam Integer yc Y coordinate of the center of the circle.
* @tparam Integer a The semi-axis of the ellipse.
* @tparam Integer b The semi-axis of the ellipse.
* @tparam Integer color The color of the ellipse.
*/
GCanvas.prototype.ellipse = function(xc,yc,a,b,color)
{
b_square=b*b;
a_square=a*a;
row=b;
col=0;
two_a_square=a_square<<1;
four_a_square=a_square<<2;
four_b_square=b_square<<2;
two_b_square=b_square<<1;
d=two_a_square*((row-1)*(row))+a_square+two_b_square*(1-a_square);
while(a_square*(row)>b_square*(col))
{
this.draw(col+xc, row+yc, color);
this.draw(col+xc, yc-row, color);
this.draw(xc-col, row+yc, color);
this.draw(xc-col, yc-row, color);
if (d>=0)
{
row--;
d-=four_a_square*(row);
}
d+=two_b_square*(3+(col<<1));
col++;
}
d=two_b_square*(col+1)*col+two_a_square*(row*(row-2)+1)+(1-two_a_square)*b_square;
while ((row) + 1)
{
this.draw(col+xc, row+yc, color);
this.draw(col+xc, yc-row, color);
this.draw(xc-col, row+yc, color);
this.draw(xc-col, yc-row, color);
if (d<=0)
{
col++;
d+=four_b_square*col;
}
row--;
d+=two_a_square*(3-(row <<1));
}
};
/**
* Fills a closed area (using stack)
* @tparam Integer x X coordinate of the point inside the area to be filled-in.
* @tparam Integer y Y coordinate of the point inside the area to be filled-in.
* @tparam Integer color Fill color.
*/
GCanvas.prototype.fill = function(x,y,color) {
stack_head=0;
stack_tail=0;
floodfill_stackx = new Array((this.width+2)*(this.height+2));
floodfill_stacky = new Array((this.width+2)*(this.height+2));
clr=this.getcolor(x,y);
floodfill_stackx[stack_head]=x;
floodfill_stacky[stack_head]=y;
this.draw(x,y,color);
stack_head++;
while ( (stack_head<((this.width+2)*(this.height+2))) && (stack_head>stack_tail) ) {
x=floodfill_stackx[stack_tail];
y=floodfill_stacky[stack_tail];
stack_tail++;
if (x>=0 && y>=0 && x<this.width && y<this.height) {
if (this.getcolor(x+1,y)==clr) {
floodfill_stackx[stack_head]=x+1;
floodfill_stacky[stack_head]=y;
this.draw(x+1,y,color);
stack_head++;
}
if (this.getcolor(x-1,y)==clr) {
floodfill_stackx[stack_head]=x-1;
floodfill_stacky[stack_head]=y;
this.draw(x-1,y,color);
stack_head++;
}
if (this.getcolor(x,y+1)==clr) {
floodfill_stackx[stack_head]=x;
floodfill_stacky[stack_head]=y+1;
this.draw(x,y+1,color);
stack_head++;
}
if (this.getcolor(x,y-1)==clr) {
floodfill_stackx[stack_head]=x;
floodfill_stacky[stack_head]=y-1;
this.draw(x,y-1,color);
stack_head++;
}
}
}
delete floodfill_stacky;
delete floodfill_stackx;
};
/**
* Draws a polyline.
* @tparam Array x x1,x2, ..., xn.
* @tparam Array y y1,y2, ..., yn.
* @tparam Integer color Polyline color.
*/
GCanvas.prototype.polyline = function(x, y, color) {
var z = x.length-1; while (z >= 0) this.line(x[z], y[z], x[--z], y[z], color);
};
/**
* Draws a polygon (automatically closed if last points are not identical.
* @tparam Array x x1,x2, ..., xn.
* @tparam Array y y1,y2, ..., yn.
* @tparam Integer color Polygon color.
*/
GCanvas.prototype.polygon = function(x, y, color) {
this.polyline(x, y, color);
this.line(x[x.length-1], y[x.length-1], x[0], y[0], color);
};
/**
* Output processor.
*
* An abstract output processor.
*
* @ctor
* Abstract output processor.
* @tparam Integer scale The scaling of the output (1 = 1x = no scaling).
*/
function GOutput(scale) {
/**
* Scaling of the output (1 = 1x = no scaling).
* @type Integer
*/
this.scale=scale || 1;
}
/**
* HTML output processor.
*
* This output processor can be used to render the canvas as an HTML table.
* Two types ("Fast RLE" and "Optimised RLE") of output rendering are provided,
* see bellow.
*
* @ctor
* Constructs an HTML output processor.
*/
function GHTMLOutput() {
/**
* Compression parametr (0 - fast RLE, 1 - optimised RLE).
* @type Integer
*/
this.rupression=0;
/**
* Output type - HTML (table) or DHTML (div"s). If dhtml is set to false HTML output
* is produced and if dhtml is set to true DHTML output is produced.
* @type Boolean
*/
this.dhtml = true;
/**
* An invsibile color. By default invisible_color = -1, i.e. the default background of the canvas.
* @type Integer
*/
this.invisible_color = -1;
/**
* Number of cells generated in the HTML table.
* @type Integer
*/
this.number_of_cells=0;
/**
* Time (in ms.) used for the generation of the HTML table.
* @type Integer
*/
this.generation_time=0;
/**
* Javascript document object (usually this.document).
*
* Needed only for print functions.
* @type Object
*/
this.doc = null;
/**
* Output layer ID.
*
* Needed only for print functions.
* @type String
*/
this.layerId = null;
/**
* Append or overwrite the layer.
*
* Needed only for print functions.
* @type Boolean
*/
this.append = false;
}
GHTMLOutput.prototype = new GOutput();
/**
* HTML output printing parametrs setup function.
*
* @tparam Object doc Document object (usually this.document).
* @tparam String layerId Output layer ID.
*/
GHTMLOutput.prototype.setup = function(doc,layerId) {
this.doc=doc;
this.layerId=layerId;
};
/**
* Returns the image canvas html (using RLE compression on lines = fast RLE).
* @treturn String A table in HTML format representing the image canvas.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.get_html = function(gcanvas) {
time_now = new Date();
this.number_of_cells = 0;
if (this.invisible_color == -1) {
inv_color = gcanvas.bgcolor;
} else {
inv_color = this.invisibile_color;
}
str = new String("");
len = 0;
if (! this.dhtml) {
str += "<table border=0 cellspacing=0 cellpadding=0 width="+gcanvas.width*this.scale+" height="+gcanvas.height*this.scale+">";
}
for (i=0; i < gcanvas.height; i++) {
if (! this.dhtml) {
str += "<tr height="+this.scale+" width="+gcanvas.width*this.scale+">";
}
current_color = gcanvas.getcolor(0,i);
len = 0;
start_j = 0;
for (j=0; j < gcanvas.width; j++) {
if ( (gcanvas.getcolor(j,i) != current_color) || (j == gcanvas.width-1)) {
if (j== gcanvas.width-1) { len++; }
if (! this.dhtml) {
str += "<td width="+this.scale*len+" height="+this.scale + ( (len>1) ? " colspan="+len : "" ) + ( (current_color!=inv_color) ? " bgcolor="+Color.inttohtml(current_color) : "") + "></td>";
} else {
if (current_color!=inv_color) {
str += "<div style="position:absolute;"+ "left:" + (start_j*this.scale) + "px;"+ "top:" + (i*this.scale) + "px;"+ "width:" + (this.scale*len) + "px;"+ "height:" + this.scale + "px;"+ "clip:rect(0,"+(this.scale*len)+"px,"+this.scale+"px,0);" + "overflow:hidden;background-color:" + Color.inttohtml(current_color) + ";" + ""><\/div>";
}
}
this.number_of_cells++;
len=1;
start_j = j;
current_color=gcanvas.getcolor(j,i);
} else {
len++;
}
}
if (! this.dhtml) {
str += "</tr>";
}
}
if (! this.dhtml) {
str += "</table>";
}
time_finish = new Date();
this.generation_time = time_finish - time_now;
delete time_now;
delete time_finish;
return str;
};
/**
* Returns the image canvas html (using RLE compression on both lines and rows = optimised RLE).
* @treturn String A table in HTML format representing the image canvas.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.get_optimised_html = function(gcanvas) {
time_now = new Date();
if (this.invisible_color == -1) {
inv_color = gcanvas.bgcolor;
} else {
inv_color = this.invisibile_color;
}
this.number_of_cells = 0;
str = new String("");
flushed = new Array(gcanvas.height*gcanvas.width);
for (i=0;i<gcanvas.height*gcanvas.width;i++) {
flushed[i]=0;
}
if (! this.dhtml) {
str += "<table border=0 cellspacing=0 cellpadding=0 width="+gcanvas.width*this.scale+" height="+gcanvas.height*this.scale+">";
}
for (i=0; i < gcanvas.height; i++) {
if (! this.dhtml) {
str += "<tr height="+this.scale+" width="+gcanvas.width*this.scale+">";
}
for (j=0; j < gcanvas.width; j++) {
if (flushed[i*gcanvas.width+j] == 0) {
current_color = gcanvas.getcolor(j,i);
k=gcanvas.height;
opt = 0;
colspan = 1;
rowspan = 1;
for (x=j; x < gcanvas.width; x++) {
if (flushed[i*gcanvas.height+x]==1) { break; }
if (gcanvas.getcolor(x,i) != current_color) { break; }
for (y=i; y < k; y++) {
if (flushed[y*gcanvas.width+x]==1) { break; }
if (gcanvas.getcolor(x,y) != current_color) { break; }
}
if (y-1<0) { break; }
if (gcanvas.getcolor(x,y-1) != current_color) { break; }
k=y;
if ( ((x-j+1)*(y-i)) > opt) {
opt=(x-j+1)*(y-i);
colspan = x-j+1;
rowspan = y-i;
}
}
for (y=i; y < i+rowspan; y++) {
for (x=j; x < j+colspan; x++) {
flushed[y*gcanvas.width+x]=1;
}
}
if (! this.dhtml) {
str += "<td width="+this.scale*colspan+" height="+ this.scale*rowspan + ( (colspan>1) ? " colspan="+colspan : "" ) + ( (rowspan > 1) ? " rowspan=" + rowspan : "") + ( (current_color!=inv_color) ? " bgcolor="+Color.inttohtml(current_color) : "") + "></td>";
} else {
if (current_color!=inv_color) {
str += "<div style="position:absolute;"+ "left:" + (j*this.scale) + "px;"+ "top:" + (i*this.scale) + "px;"+ "width:" + (this.scale*colspan) + "px;"+ "height:" + (this.scale*rowspan) + "px;"+ "clip:rect(0,"+(this.scale*colspan)+"px,"+(this.scale*rowspan)+"px,0);" + "overflow:hidden;background-color:" + Color.inttohtml(current_color) + ";" + ""><\/div>";
}
}
this.number_of_cells++;
}
}
if (! this.dhtml) {
str += "</tr>";
}
}
if (! this.dhtml) {
str += "</table>";
}
delete flushed;
time_finish = new Date();
this.generation_time = time_finish - time_now;
delete time_now;
delete time_finish;
return str;
};
/**
* Assigns the image canvas html (using RLE compression on lines = fast RLE) to a given layer.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.print_html = function(gcanvas) {
if ((this.doc != null) && (this.layerId != null)) {
if (this.doc.all) {
outlayer = this.doc.all[this.layerId];
}
if (this.doc.getElementById) {
outlayer = this.doc.getElementById(this.layerId);
}
if (this.append) {
outlayer.innerHTML += this.get_html(gcanvas);
} else {
outlayer.innerHTML = this.get_html(gcanvas);
}
}
};
/**
* Assigns the image canvas html (using RLE compression on both lines and rows = optimised RLE) to a given layer.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.print_optimised_html = function(gcanvas) {
if ((this.doc != null) && (this.layerId != null)) {
if (this.doc.all) {
outlayer = this.doc.all[this.layerId];
}
if (this.doc.getElementById) {
outlayer = this.doc.getElementById(this.layerId);
}
if (this.append) {
outlayer.innerHTML += this.get_optimised_html(gcanvas);
} else {
outlayer.innerHTML = this.get_optimised_html(gcanvas);
}
}
};
/**
* Returns the image canvas html
* @treturn String A table in HTML format representing the image canvas.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.get = function(gcanvas) {
switch (this.rupression) {
case 0: return this.get_html(gcanvas); break;
case 1: return this.get_optimised_html(gcanvas); break;
default: return this.get_html(gcanvas); break;
}
};
/**
* Assigns the image canvas html to a given layer.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GHTMLOutput.prototype.print = function(gcanvas) {
switch (this.rupression) {
case 0: return this.print_html(gcanvas); break;
case 1: return this.print_optimised_html(gcanvas); break;
default: return this.print_html(gcanvas); break;
}
}
/**
* Java (applet) output processor.
*
* This output processor can be used to pass your canvas to a Java applet for rendering.
*
* @ctor
* Constructs an applet output processor.
*/
function GJavaOutput() {
/**
* Time (in ms.) used for the generation of the image string to be passed to the applet.
* @type Integer
*/
this.generation_time=0;
/**
* Javascript document object (usually this.document).
*
* Needed only for print function.
* @type Object
*/
this.doc = null;
/**
* The name of an applet providing setImage function.
*
* Example of an applet providing setImage function:
* <PRE>
* -----------------------------------------------------------
*
* import java.applet.Applet;
* import java.awt.Graphics;
* import java.awt.Image;
* import java.awt.ruponent;
* import java.lang.Integer;
* import java.util.StringTokenizer;
*
* public class ImageOutput extends Applet {
* Image JSImage = null;
*
* public void init() {
* // some initialisation here
* }
*
* public void paint(Graphics g) {
* if (this.JSImage != null) {
* g.drawImage(this.JSImage, 0, 0, this);
* }
* }
*
* public void setImage(int w, int h, String pixels) {
* int pix[] = new int[w * h];
* StringTokenizer st = new StringTokenizer(pixels," ");
* int index = 0;
* while (st.hasMoreTokens()) {
* pix[index++]=Integer.parseInt(st.nextToken());
* }
* this.JSImage = createImage(new java.awt.image.MemoryImageSource(w, h, pix, 0, w));
* repaint();
* }
* }
*
* -----------------------------------------------------------
* </PRE>
* Javascript is used to passed a String of the image bytes separated by space. Array would
* be a better choice, but it seems that MS IE fails to pass JavaScript Array to Java correctly.
*
* Needed only for print function.
* @type String
*/
this.appletName = null;
/**
* Alpha chanel value.
* @type Integer
*/
this.alpha = 255;
}
GJavaOutput.prototype = new GOutput();
/**
* Java output printing parametrs setup function (needed only for print functions).
*
* @tparam Object doc Document object (usually this.document).
* @tparam String appletName Reciving java applet name.
*/
GJavaOutput.prototype.setup = function(doc,appletName) {
this.doc=doc;
this.appletName=appletName;
};
/**
* Returns the image canvas string to be passed to Java.
* @treturn String String representing the bytes of the image separated by spaces;
* @tparam GCanvas gcanvas 2D image canvas.
*/
GJavaOutput.prototype.get = function(gcanvas) {
time_now = new Date();
pixels = new String("");
for (y=0;y<gcanvas.height;y++) {
for (i=0;i<this.scale;i++) {
for (x=0;x<gcanvas.width;x++) {
for (j=0;j<this.scale;j++) {
pixels += (pixels.length>0?" ":"") + ((this.alpha << 24) | gcanvas.getcolor(x,y));
}
}
}
}
time_finish = new Date();
this.generation_time = time_finish - time_now;
delete time_now;
delete time_finish;
return pixels;
};
/**
* Passes the image canvas String to a given applet.
* @tparam GCanvas gcanvas 2D image canvas.
*/
GJavaOutput.prototype.print = function(gcanvas) {
if ((this.doc != null) && (this.appletName != null)) {
this.doc.applets[this.appletName].setImage(gcanvas.width*this.scale,gcanvas.height*this.scale,this.get(gcanvas));
}
};
</script>
<script type="text/javascript">
<!--
// Usage examples
var white = Color.htmltoint("#FFFFFF");
var black = Color.htmltoint("#000000");
var gc = new GCanvas(200,100,white,1);
var output = new GHTMLOutput();
output.dhtml = true;
output.append = true;
output.scale=2;
// color lines
var colors = new Array("#000000", "#C0C0C0", "#808080", "#800000", "#FF0000", "#800080", "#FF00FF", "#008000", "#00FF00", "#808000", "#FFFF00", "#000080", "#0000FF", "#008080", "#00FFFF");
var lines_runs = 0;
var sx1 = 0;
var sx = 0;
var sy1 = Math.round(Math.random()*gc.height);
function lines() {
gc.clear();
lc = Math.round(Math.random()*(colors.length-1));
if (sx < gc.width) {
sx = sx1 + 10;
} else {
sx = 0;
}
sy = Math.round(Math.random()*gc.height);
gc.line(sx1,sy1,sx,sy, Color.htmltoint(colors[lc]));
sx1 = sx;
sy1 = sy;
output.setup(this.document,"lines");
output.print(gc);
result = "Line from " + sx1 + ", " + sy1 + " to " + sx + ", " + sy + " was rendered in " + output.generation_time + " ms.";
if (this.document.all) {
this.document.all["time"].innerHTML = result;
}
if (this.document.getElementById) {
this.document.getElementById("time").innerHTML = result;
}
if (lines_runs == 1) {
setTimeout("lines()", 1000);
}
}
// -->
</script>
</head>
<body onLoad="lines(); return true;">
<h1>On Top</h1>
<form>
Click <input type="button" value="Start!" name="start_lines" class="active" onClick="if (lines_runs==0) { this.form.start_lines.className="passive"; this.form.stop_lines.className="active"; lines_runs=1; lines(); }"> to start drawing lines.
Click <input type="button" value="Stop!" name="stop_lines" class="passive" onClick="this.form.start_lines.className="active"; this.form.stop_lines.className="passive"; lines_runs=0;"> to stop drawing lines.
</form>
<div id="lines" style="position:relative;top:0;left:0;height:200;width:400;"><font size="+2">If you setup HTML output processor to DHTML mode you can use DHTML overlay feature to draw your image on top of the document contents.
In this example random lines are drawn on top of this text.</font></div>
Info: <div id="time" style="position:relative;top:0;left:0;height:10;width:800;"></div>
<p><div align="right">Next || <a href="./eyes.html">Previous</a> || <a href="../html/index.html#examples">Index</a></div>
</body>
</html>
Line Width
<!DOCTYPE html>
<html>
<head>
<title>Line Width Test</title>
<style>
body {
text-align: center
}
</style>
<script type="text/javascript">
/*
ExplorerCanvas
Copyright 2006 Google Inc.
-------------------------------------------------------------------------------
DESCRIPTION
Firefox, Safari and Opera 9 support the canvas tag to allow 2D command-based
drawing operations. ExplorerCanvas brings the same functionality to Internet
Explorer; web developers only need to include a single script tag in their
existing canvas webpages to enable this support.
-------------------------------------------------------------------------------
INSTALLATION
Include the ExplorerCanvas tag in the same directory as your HTML files, and
add the following code to your page, preferably in the <head> tag.
If you run into trouble, please look at the included example code to see how
to best implement this
ExplorerCanvas
Google Open Source:
<http://code.google.ru>
<opensource@google.ru>
Developers:
Emil A Eklund <emil@eae.net>
Erik Arvidsson <erik@eae.net>
Glen Murphy <glen@glenmurphy.ru>
*/
// Copyright 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Known Issues:
//
// * Patterns are not implemented.
// * Radial gradient are not implemented. The VML version of these look very
// different from the canvas one.
// * Clipping paths are not implemented.
// * Coordsize. The width and height attribute have higher priority than the
// width and height style values which isn"t correct.
// * Painting mode isn"t implemented.
// * Canvas width/height should is using content-box by default. IE in
// Quirks mode will draw the canvas using border-box. Either change your
// doctype to HTML5
// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
// or use Box Sizing Behavior from WebFX
// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
// * Optimize. There is always room for speed improvements.
// only add this code if we do not already have a canvas implementation
if (!window.CanvasRenderingContext2D) {
(function () {
// alias some functions to make (compiled) code shorter
var m = Math;
var mr = m.round;
var ms = m.sin;
var mc = m.cos;
// this is used for sub pixel precision
var Z = 10;
var Z2 = Z / 2;
var G_vmlCanvasManager_ = {
init: function (opt_doc) {
var doc = opt_doc || document;
if (/MSIE/.test(navigator.userAgent) && !window.opera) {
var self = this;
doc.attachEvent("onreadystatechange", function () {
self.init_(doc);
});
}
},
init_: function (doc) {
if (doc.readyState == "complete") {
// create xmlns
if (!doc.namespaces["g_vml_"]) {
doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
}
// setup default css
var ss = doc.createStyleSheet();
ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
// default size is 300x150 in Gecko and Opera
"text-align:left;width:300px;height:150px}" +
"g_vml_\\:*{behavior:url(#default#VML)}";
// find all canvas elements
var els = doc.getElementsByTagName("canvas");
for (var i = 0; i < els.length; i++) {
if (!els[i].getContext) {
this.initElement(els[i]);
}
}
}
},
fixElement_: function (el) {
// in IE before version 5.5 we would need to add HTML: to the tag name
// but we do not care about IE before version 6
var outerHTML = el.outerHTML;
var newEl = el.ownerDocument.createElement(outerHTML);
// if the tag is still open IE has created the children as siblings and
// it has also created a tag with the name "/FOO"
if (outerHTML.slice(-2) != "/>") {
var tagName = "/" + el.tagName;
var ns;
// remove content
while ((ns = el.nextSibling) && ns.tagName != tagName) {
ns.removeNode();
}
// remove the incorrect closing tag
if (ns) {
ns.removeNode();
}
}
el.parentNode.replaceChild(newEl, el);
return newEl;
},
/**
* Public initializes a canvas element so that it can be used as canvas
* element from now on. This is called automatically before the page is
* loaded but if you are creating elements using createElement you need to
* make sure this is called on the element.
* @param {HTMLElement} el The canvas element to initialize.
* @return {HTMLElement} the element that was created.
*/
initElement: function (el) {
el = this.fixElement_(el);
el.getContext = function () {
if (this.context_) {
return this.context_;
}
return this.context_ = new CanvasRenderingContext2D_(this);
};
// do not use inline function because that will leak memory
el.attachEvent("onpropertychange", onPropertyChange);
el.attachEvent("onresize", onResize);
var attrs = el.attributes;
if (attrs.width && attrs.width.specified) {
// TODO: use runtimeStyle and coordsize
// el.getContext().setWidth_(attrs.width.nodeValue);
el.style.width = attrs.width.nodeValue + "px";
} else {
el.width = el.clientWidth;
}
if (attrs.height && attrs.height.specified) {
// TODO: use runtimeStyle and coordsize
// el.getContext().setHeight_(attrs.height.nodeValue);
el.style.height = attrs.height.nodeValue + "px";
} else {
el.height = el.clientHeight;
}
//el.getContext().setCoordsize_()
return el;
}
};
function onPropertyChange(e) {
var el = e.srcElement;
switch (e.propertyName) {
case "width":
el.style.width = el.attributes.width.nodeValue + "px";
el.getContext().clearRect();
break;
case "height":
el.style.height = el.attributes.height.nodeValue + "px";
el.getContext().clearRect();
break;
}
}
function onResize(e) {
var el = e.srcElement;
if (el.firstChild) {
el.firstChild.style.width = el.clientWidth + "px";
el.firstChild.style.height = el.clientHeight + "px";
}
}
G_vmlCanvasManager_.init();
// precompute "00" to "FF"
var dec2hex = [];
for (var i = 0; i < 16; i++) {
for (var j = 0; j < 16; j++) {
dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
}
}
function createMatrixIdentity() {
return [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]
];
}
function matrixMultiply(m1, m2) {
var result = createMatrixIdentity();
for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
var sum = 0;
for (var z = 0; z < 3; z++) {
sum += m1[x][z] * m2[z][y];
}
result[x][y] = sum;
}
}
return result;
}
function copyState(o1, o2) {
o2.fillStyle = o1.fillStyle;
o2.lineCap = o1.lineCap;
o2.lineJoin = o1.lineJoin;
o2.lineWidth = o1.lineWidth;
o2.miterLimit = o1.miterLimit;
o2.shadowBlur = o1.shadowBlur;
o2.shadowColor = o1.shadowColor;
o2.shadowOffsetX = o1.shadowOffsetX;
o2.shadowOffsetY = o1.shadowOffsetY;
o2.strokeStyle = o1.strokeStyle;
o2.arcScaleX_ = o1.arcScaleX_;
o2.arcScaleY_ = o1.arcScaleY_;
}
function processStyle(styleString) {
var str, alpha = 1;
styleString = String(styleString);
if (styleString.substring(0, 3) == "rgb") {
var start = styleString.indexOf("(", 3);
var end = styleString.indexOf(")", start + 1);
var guts = styleString.substring(start + 1, end).split(",");
str = "#";
for (var i = 0; i < 3; i++) {
str += dec2hex[Number(guts[i])];
}
if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
alpha = guts[3];
}
} else {
str = styleString;
}
return [str, alpha];
}
function processLineCap(lineCap) {
switch (lineCap) {
case "butt":
return "flat";
case "round":
return "round";
case "square":
default:
return "square";
}
}
/**
* This class implements CanvasRenderingContext2D interface as described by
* the WHATWG.
* @param {HTMLElement} surfaceElement The element that the 2D context should
* be associated with
*/
function CanvasRenderingContext2D_(surfaceElement) {
this.m_ = createMatrixIdentity();
this.mStack_ = [];
this.aStack_ = [];
this.currentPath_ = [];
// Canvas context properties
this.strokeStyle = "#000";
this.fillStyle = "#000";
this.lineWidth = 1;
this.lineJoin = "miter";
this.lineCap = "butt";
this.miterLimit = Z * 1;
this.globalAlpha = 1;
this.canvas = surfaceElement;
var el = surfaceElement.ownerDocument.createElement("div");
el.style.width = surfaceElement.clientWidth + "px";
el.style.height = surfaceElement.clientHeight + "px";
el.style.overflow = "hidden";
el.style.position = "absolute";
surfaceElement.appendChild(el);
this.element_ = el;
this.arcScaleX_ = 1;
this.arcScaleY_ = 1;
};
var contextPrototype = CanvasRenderingContext2D_.prototype;
contextPrototype.clearRect = function() {
this.element_.innerHTML = "";
this.currentPath_ = [];
};
contextPrototype.beginPath = function() {
// TODO: Branch current matrix so that save/restore has no effect
// as per safari docs.
this.currentPath_ = [];
};
contextPrototype.moveTo = function(aX, aY) {
this.currentPath_.push({type: "moveTo", x: aX, y: aY});
this.currentX_ = aX;
this.currentY_ = aY;
};
contextPrototype.lineTo = function(aX, aY) {
this.currentPath_.push({type: "lineTo", x: aX, y: aY});
this.currentX_ = aX;
this.currentY_ = aY;
};
contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
aCP2x, aCP2y,
aX, aY) {
this.currentPath_.push({type: "bezierCurveTo",
cp1x: aCP1x,
cp1y: aCP1y,
cp2x: aCP2x,
cp2y: aCP2y,
x: aX,
y: aY});
this.currentX_ = aX;
this.currentY_ = aY;
};
contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
// the following is lifted almost directly from
// http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_);
var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_);
var cp2x = cp1x + (aX - this.currentX_) / 3.0;
var cp2y = cp1y + (aY - this.currentY_) / 3.0;
this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY);
};
contextPrototype.arc = function(aX, aY, aRadius,
aStartAngle, aEndAngle, aClockwise) {
aRadius *= Z;
var arcType = aClockwise ? "at" : "wa";
var xStart = aX + (mc(aStartAngle) * aRadius) - Z2;
var yStart = aY + (ms(aStartAngle) * aRadius) - Z2;
var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2;
var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2;
// IE won"t render arches drawn counter clockwise if xStart == xEnd.
if (xStart == xEnd && !aClockwise) {
xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
// that can be represented in binary
}
this.currentPath_.push({type: arcType,
x: aX,
y: aY,
radius: aRadius,
xStart: xStart,
yStart: yStart,
xEnd: xEnd,
yEnd: yEnd});
};
contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
};
contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
// Will destroy any existing path (same as FF behaviour)
this.beginPath();
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
this.stroke();
};
contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
// Will destroy any existing path (same as FF behaviour)
this.beginPath();
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
this.fill();
};
contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
var gradient = new CanvasGradient_("gradient");
return gradient;
};
contextPrototype.createRadialGradient = function(aX0, aY0,
aR0, aX1,
aY1, aR1) {
var gradient = new CanvasGradient_("gradientradial");
gradient.radius1_ = aR0;
gradient.radius2_ = aR1;
gradient.focus_.x = aX0;
gradient.focus_.y = aY0;
return gradient;
};
contextPrototype.drawImage = function (image, var_args) {
var dx, dy, dw, dh, sx, sy, sw, sh;
// to find the original width we overide the width and height
var oldRuntimeWidth = image.runtimeStyle.width;
var oldRuntimeHeight = image.runtimeStyle.height;
image.runtimeStyle.width = "auto";
image.runtimeStyle.height = "auto";
// get the original size
var w = image.width;
var h = image.height;
// and remove overides
image.runtimeStyle.width = oldRuntimeWidth;
image.runtimeStyle.height = oldRuntimeHeight;
if (arguments.length == 3) {
dx = arguments[1];
dy = arguments[2];
sx = sy = 0;
sw = dw = w;
sh = dh = h;
} else if (arguments.length == 5) {
dx = arguments[1];
dy = arguments[2];
dw = arguments[3];
dh = arguments[4];
sx = sy = 0;
sw = w;
sh = h;
} else if (arguments.length == 9) {
sx = arguments[1];
sy = arguments[2];
sw = arguments[3];
sh = arguments[4];
dx = arguments[5];
dy = arguments[6];
dw = arguments[7];
dh = arguments[8];
} else {
throw "Invalid number of arguments";
}
var d = this.getCoords_(dx, dy);
var w2 = sw / 2;
var h2 = sh / 2;
var vmlStr = [];
var W = 10;
var H = 10;
// For some reason that I"ve now forgotten, using divs didn"t work
vmlStr.push(" <g_vml_:group",
" coordsize="", Z * W, ",", Z * H, """,
" coordorigin="0,0"" ,
" style="width:", W, ";height:", H, ";position:absolute;");
// If filters are necessary (rotation exists), create them
// filters are bog-slow, so only create them if abbsolutely necessary
// The following check doesn"t account for skews (which don"t exist
// in the canvas spec (yet) anyway.
if (this.m_[0][0] != 1 || this.m_[0][1]) {
var filter = [];
// Note the 12/21 reversal
filter.push("M11="", this.m_[0][0], "",",
"M12="", this.m_[1][0], "",",
"M21="", this.m_[0][1], "",",
"M22="", this.m_[1][1], "",",
"Dx="", mr(d.x / Z), "",",
"Dy="", mr(d.y / Z), """);
// Bounding box calculation (need to minimize displayed area so that
// filters don"t waste time on unused pixels.
var max = d;
var c2 = this.getCoords_(dx + dw, dy);
var c3 = this.getCoords_(dx, dy + dh);
var c4 = this.getCoords_(dx + dw, dy + dh);
max.x = Math.max(max.x, c2.x, c3.x, c4.x);
max.y = Math.max(max.y, c2.y, c3.y, c4.y);
vmlStr.push("padding:0 ", mr(max.x / Z), "px ", mr(max.y / Z),
"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
filter.join(""), ", sizingmethod="clip");")
} else {
vmlStr.push("top:", mr(d.y / Z), "px;left:", mr(d.x / Z), "px;")
}
vmlStr.push(" ">" ,
"<g_vml_:image src="", image.src, """,
" style="width:", Z * dw, ";",
" height:", Z * dh, ";"",
" cropleft="", sx / w, """,
" croptop="", sy / h, """,
" cropright="", (w - sx - sw) / w, """,
" cropbottom="", (h - sy - sh) / h, """,
" />",
"</g_vml_:group>");
this.element_.insertAdjacentHTML("BeforeEnd",
vmlStr.join(""));
};
contextPrototype.stroke = function(aFill) {
var lineStr = [];
var lineOpen = false;
var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
var color = a[0];
var opacity = a[1] * this.globalAlpha;
var W = 10;
var H = 10;
lineStr.push("<g_vml_:shape",
" fillcolor="", color, """,
" filled="", Boolean(aFill), """,
" style="position:absolute;width:", W, ";height:", H, ";"",
" coordorigin="0 0" coordsize="", Z * W, " ", Z * H, """,
" stroked="", !aFill, """,
" strokeweight="", this.lineWidth, """,
" strokecolor="", color, """,
" path="");
var newSeq = false;
var min = {x: null, y: null};
var max = {x: null, y: null};
for (var i = 0; i < this.currentPath_.length; i++) {
var p = this.currentPath_[i];
if (p.type == "moveTo") {
lineStr.push(" m ");
var c = this.getCoords_(p.x, p.y);
lineStr.push(mr(c.x), ",", mr(c.y));
} else if (p.type == "lineTo") {
lineStr.push(" l ");
var c = this.getCoords_(p.x, p.y);
lineStr.push(mr(c.x), ",", mr(c.y));
} else if (p.type == "close") {
lineStr.push(" x ");
} else if (p.type == "bezierCurveTo") {
lineStr.push(" c ");
var c = this.getCoords_(p.x, p.y);
var c1 = this.getCoords_(p.cp1x, p.cp1y);
var c2 = this.getCoords_(p.cp2x, p.cp2y);
lineStr.push(mr(c1.x), ",", mr(c1.y), ",",
mr(c2.x), ",", mr(c2.y), ",",
mr(c.x), ",", mr(c.y));
} else if (p.type == "at" || p.type == "wa") {
lineStr.push(" ", p.type, " ");
var c = this.getCoords_(p.x, p.y);
var cStart = this.getCoords_(p.xStart, p.yStart);
var cEnd = this.getCoords_(p.xEnd, p.yEnd);
lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",",
mr(c.y - this.arcScaleY_ * p.radius), " ",
mr(c.x + this.arcScaleX_ * p.radius), ",",
mr(c.y + this.arcScaleY_ * p.radius), " ",
mr(cStart.x), ",", mr(cStart.y), " ",
mr(cEnd.x), ",", mr(cEnd.y));
}
// TODO: Following is broken for curves due to
// move to proper paths.
// Figure out dimensions so we can do gradient fills
// properly
if(c) {
if (min.x == null || c.x < min.x) {
min.x = c.x;
}
if (max.x == null || c.x > max.x) {
max.x = c.x;
}
if (min.y == null || c.y < min.y) {
min.y = c.y;
}
if (max.y == null || c.y > max.y) {
max.y = c.y;
}
}
}
lineStr.push(" ">");
if (typeof this.fillStyle == "object") {
var focus = {x: "50%", y: "50%"};
var width = (max.x - min.x);
var height = (max.y - min.y);
var dimension = (width > height) ? width : height;
focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%";
var colors = [];
// inside radius (%)
if (this.fillStyle.type_ == "gradientradial") {
var inside = (this.fillStyle.radius1_ / dimension * 100);
// percentage that outside radius exceeds inside radius
var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
} else {
var inside = 0;
var expansion = 100;
}
var insidecolor = {offset: null, color: null};
var outsidecolor = {offset: null, color: null};
// We need to sort "colors" by percentage, from 0 > 100 otherwise ie
// won"t interpret it correctly
this.fillStyle.colors_.sort(function (cs1, cs2) {
return cs1.offset - cs2.offset;
});
for (var i = 0; i < this.fillStyle.colors_.length; i++) {
var fs = this.fillStyle.colors_[i];
colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");
if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
insidecolor.offset = fs.offset;
insidecolor.color = fs.color;
}
if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
outsidecolor.offset = fs.offset;
outsidecolor.color = fs.color;
}
}
colors.pop();
lineStr.push("<g_vml_:fill",
" color="", outsidecolor.color, """,
" color2="", insidecolor.color, """,
" type="", this.fillStyle.type_, """,
" focusposition="", focus.x, ", ", focus.y, """,
" colors="", colors.join(""), """,
" opacity="", opacity, "" />");
} else if (aFill) {
lineStr.push("<g_vml_:fill color="", color, "" opacity="", opacity, "" />");
} else {
lineStr.push(
"<g_vml_:stroke",
" opacity="", opacity,""",
" joinstyle="", this.lineJoin, """,
" miterlimit="", this.miterLimit, """,
" endcap="", processLineCap(this.lineCap) ,""",
" weight="", this.lineWidth, "px"",
" color="", color,"" />"
);
}
lineStr.push("</g_vml_:shape>");
this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));
this.currentPath_ = [];
};
contextPrototype.fill = function() {
this.stroke(true);
}
contextPrototype.closePath = function() {
this.currentPath_.push({type: "close"});
};
/**
* @private
*/
contextPrototype.getCoords_ = function(aX, aY) {
return {
x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2,
y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2
}
};
contextPrototype.save = function() {
var o = {};
copyState(this, o);
this.aStack_.push(o);
this.mStack_.push(this.m_);
this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
};
contextPrototype.restore = function() {
copyState(this.aStack_.pop(), this);
this.m_ = this.mStack_.pop();
};
contextPrototype.translate = function(aX, aY) {
var m1 = [
[1, 0, 0],
[0, 1, 0],
[aX, aY, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
contextPrototype.rotate = function(aRot) {
var c = mc(aRot);
var s = ms(aRot);
var m1 = [
[c, s, 0],
[-s, c, 0],
[0, 0, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
contextPrototype.scale = function(aX, aY) {
this.arcScaleX_ *= aX;
this.arcScaleY_ *= aY;
var m1 = [
[aX, 0, 0],
[0, aY, 0],
[0, 0, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
/******** STUBS ********/
contextPrototype.clip = function() {
// TODO: Implement
};
contextPrototype.arcTo = function() {
// TODO: Implement
};
contextPrototype.createPattern = function() {
return new CanvasPattern_;
};
// Gradient / Pattern Stubs
function CanvasGradient_(aType) {
this.type_ = aType;
this.radius1_ = 0;
this.radius2_ = 0;
this.colors_ = [];
this.focus_ = {x: 0, y: 0};
}
CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
aColor = processStyle(aColor);
this.colors_.push({offset: 1-aOffset, color: aColor});
};
function CanvasPattern_() {}
// set up externs
G_vmlCanvasManager = G_vmlCanvasManager_;
CanvasRenderingContext2D = CanvasRenderingContext2D_;
CanvasGradient = CanvasGradient_;
CanvasPattern = CanvasPattern_;
})();
} // if
</script>
<script>
window.onload = function() {
var ctx = document.getElementById("c").getContext("2d");
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
for (var i = 0; i < 100; i++) {
ctx.beginPath();
ctx.moveTo(49 + i / 100, i);
ctx.lineTo(49 + i / 100, i + 1);
ctx.closePath();
ctx.stroke();
}
for (var i = 0; i < 100; i++) {
ctx.beginPath();
ctx.moveTo(i, 49 + i / 100);
ctx.lineTo(i + 1, 49 + i / 100);
ctx.closePath();
ctx.stroke();
}
};
</script>
</head>
<body>
<canvas id=c width=100 height=100></canvas>
<p>This tests that lines work in the same way in different browsers when you are
using sub pixel coordinates.</p>
</body>
</html>
Quadratic Curve Test
<!DOCTYPE html>
<html>
<head>
<title>Quadratic Curve Test</title>
<style>
body {
text-align: center
}
</style>
<script type="text/javascript">
/*
ExplorerCanvas
Copyright 2006 Google Inc.
-------------------------------------------------------------------------------
DESCRIPTION
Firefox, Safari and Opera 9 support the canvas tag to allow 2D command-based
drawing operations. ExplorerCanvas brings the same functionality to Internet
Explorer; web developers only need to include a single script tag in their
existing canvas webpages to enable this support.
ExplorerCanvas
Google Open Source:
<http://code.google.ru>
<opensource@google.ru>
Developers:
Emil A Eklund <emil@eae.net>
Erik Arvidsson <erik@eae.net>
Glen Murphy <glen@glenmurphy.ru>
*/ // Copyright 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Known Issues:
//
// * Patterns are not implemented.
// * Radial gradient are not implemented. The VML version of these look very
// different from the canvas one.
// * Clipping paths are not implemented.
// * Coordsize. The width and height attribute have higher priority than the
// width and height style values which isn"t correct.
// * Painting mode isn"t implemented.
// * Canvas width/height should is using content-box by default. IE in
// Quirks mode will draw the canvas using border-box. Either change your
// doctype to HTML5
// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
// or use Box Sizing Behavior from WebFX
// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
// * Optimize. There is always room for speed improvements.
// only add this code if we do not already have a canvas implementation
if (!window.CanvasRenderingContext2D) {
(function () {
// alias some functions to make (compiled) code shorter
var m = Math;
var mr = m.round;
var ms = m.sin;
var mc = m.cos;
// this is used for sub pixel precision
var Z = 10;
var Z2 = Z / 2;
var G_vmlCanvasManager_ = {
init: function (opt_doc) {
var doc = opt_doc || document;
if (/MSIE/.test(navigator.userAgent) && !window.opera) {
var self = this;
doc.attachEvent("onreadystatechange", function () {
self.init_(doc);
});
}
},
init_: function (doc) {
if (doc.readyState == "complete") {
// create xmlns
if (!doc.namespaces["g_vml_"]) {
doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
}
// setup default css
var ss = doc.createStyleSheet();
ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
// default size is 300x150 in Gecko and Opera
"text-align:left;width:300px;height:150px}" +
"g_vml_\\:*{behavior:url(#default#VML)}";
// find all canvas elements
var els = doc.getElementsByTagName("canvas");
for (var i = 0; i < els.length; i++) {
if (!els[i].getContext) {
this.initElement(els[i]);
}
}
}
},
fixElement_: function (el) {
// in IE before version 5.5 we would need to add HTML: to the tag name
// but we do not care about IE before version 6
var outerHTML = el.outerHTML;
var newEl = el.ownerDocument.createElement(outerHTML);
// if the tag is still open IE has created the children as siblings and
// it has also created a tag with the name "/FOO"
if (outerHTML.slice(-2) != "/>") {
var tagName = "/" + el.tagName;
var ns;
// remove content
while ((ns = el.nextSibling) && ns.tagName != tagName) {
ns.removeNode();
}
// remove the incorrect closing tag
if (ns) {
ns.removeNode();
}
}
el.parentNode.replaceChild(newEl, el);
return newEl;
},
/**
* Public initializes a canvas element so that it can be used as canvas
* element from now on. This is called automatically before the page is
* loaded but if you are creating elements using createElement you need to
* make sure this is called on the element.
* @param {HTMLElement} el The canvas element to initialize.
* @return {HTMLElement} the element that was created.
*/
initElement: function (el) {
el = this.fixElement_(el);
el.getContext = function () {
if (this.context_) {
return this.context_;
}
return this.context_ = new CanvasRenderingContext2D_(this);
};
// do not use inline function because that will leak memory
el.attachEvent("onpropertychange", onPropertyChange);
el.attachEvent("onresize", onResize);
var attrs = el.attributes;
if (attrs.width && attrs.width.specified) {
// TODO: use runtimeStyle and coordsize
// el.getContext().setWidth_(attrs.width.nodeValue);
el.style.width = attrs.width.nodeValue + "px";
} else {
el.width = el.clientWidth;
}
if (attrs.height && attrs.height.specified) {
// TODO: use runtimeStyle and coordsize
// el.getContext().setHeight_(attrs.height.nodeValue);
el.style.height = attrs.height.nodeValue + "px";
} else {
el.height = el.clientHeight;
}
//el.getContext().setCoordsize_()
return el;
}
};
function onPropertyChange(e) {
var el = e.srcElement;
switch (e.propertyName) {
case "width":
el.style.width = el.attributes.width.nodeValue + "px";
el.getContext().clearRect();
break;
case "height":
el.style.height = el.attributes.height.nodeValue + "px";
el.getContext().clearRect();
break;
}
}
function onResize(e) {
var el = e.srcElement;
if (el.firstChild) {
el.firstChild.style.width = el.clientWidth + "px";
el.firstChild.style.height = el.clientHeight + "px";
}
}
G_vmlCanvasManager_.init();
// precompute "00" to "FF"
var dec2hex = [];
for (var i = 0; i < 16; i++) {
for (var j = 0; j < 16; j++) {
dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
}
}
function createMatrixIdentity() {
return [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]
];
}
function matrixMultiply(m1, m2) {
var result = createMatrixIdentity();
for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
var sum = 0;
for (var z = 0; z < 3; z++) {
sum += m1[x][z] * m2[z][y];
}
result[x][y] = sum;
}
}
return result;
}
function copyState(o1, o2) {
o2.fillStyle = o1.fillStyle;
o2.lineCap = o1.lineCap;
o2.lineJoin = o1.lineJoin;
o2.lineWidth = o1.lineWidth;
o2.miterLimit = o1.miterLimit;
o2.shadowBlur = o1.shadowBlur;
o2.shadowColor = o1.shadowColor;
o2.shadowOffsetX = o1.shadowOffsetX;
o2.shadowOffsetY = o1.shadowOffsetY;
o2.strokeStyle = o1.strokeStyle;
o2.arcScaleX_ = o1.arcScaleX_;
o2.arcScaleY_ = o1.arcScaleY_;
}
function processStyle(styleString) {
var str, alpha = 1;
styleString = String(styleString);
if (styleString.substring(0, 3) == "rgb") {
var start = styleString.indexOf("(", 3);
var end = styleString.indexOf(")", start + 1);
var guts = styleString.substring(start + 1, end).split(",");
str = "#";
for (var i = 0; i < 3; i++) {
str += dec2hex[Number(guts[i])];
}
if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
alpha = guts[3];
}
} else {
str = styleString;
}
return [str, alpha];
}
function processLineCap(lineCap) {
switch (lineCap) {
case "butt":
return "flat";
case "round":
return "round";
case "square":
default:
return "square";
}
}
/**
* This class implements CanvasRenderingContext2D interface as described by
* the WHATWG.
* @param {HTMLElement} surfaceElement The element that the 2D context should
* be associated with
*/
function CanvasRenderingContext2D_(surfaceElement) {
this.m_ = createMatrixIdentity();
this.mStack_ = [];
this.aStack_ = [];
this.currentPath_ = [];
// Canvas context properties
this.strokeStyle = "#000";
this.fillStyle = "#000";
this.lineWidth = 1;
this.lineJoin = "miter";
this.lineCap = "butt";
this.miterLimit = Z * 1;
this.globalAlpha = 1;
this.canvas = surfaceElement;
var el = surfaceElement.ownerDocument.createElement("div");
el.style.width = surfaceElement.clientWidth + "px";
el.style.height = surfaceElement.clientHeight + "px";
el.style.overflow = "hidden";
el.style.position = "absolute";
surfaceElement.appendChild(el);
this.element_ = el;
this.arcScaleX_ = 1;
this.arcScaleY_ = 1;
};
var contextPrototype = CanvasRenderingContext2D_.prototype;
contextPrototype.clearRect = function() {
this.element_.innerHTML = "";
this.currentPath_ = [];
};
contextPrototype.beginPath = function() {
// TODO: Branch current matrix so that save/restore has no effect
// as per safari docs.
this.currentPath_ = [];
};
contextPrototype.moveTo = function(aX, aY) {
this.currentPath_.push({type: "moveTo", x: aX, y: aY});
this.currentX_ = aX;
this.currentY_ = aY;
};
contextPrototype.lineTo = function(aX, aY) {
this.currentPath_.push({type: "lineTo", x: aX, y: aY});
this.currentX_ = aX;
this.currentY_ = aY;
};
contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
aCP2x, aCP2y,
aX, aY) {
this.currentPath_.push({type: "bezierCurveTo",
cp1x: aCP1x,
cp1y: aCP1y,
cp2x: aCP2x,
cp2y: aCP2y,
x: aX,
y: aY});
this.currentX_ = aX;
this.currentY_ = aY;
};
contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
// the following is lifted almost directly from
// http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_);
var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_);
var cp2x = cp1x + (aX - this.currentX_) / 3.0;
var cp2y = cp1y + (aY - this.currentY_) / 3.0;
this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY);
};
contextPrototype.arc = function(aX, aY, aRadius,
aStartAngle, aEndAngle, aClockwise) {
aRadius *= Z;
var arcType = aClockwise ? "at" : "wa";
var xStart = aX + (mc(aStartAngle) * aRadius) - Z2;
var yStart = aY + (ms(aStartAngle) * aRadius) - Z2;
var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2;
var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2;
// IE won"t render arches drawn counter clockwise if xStart == xEnd.
if (xStart == xEnd && !aClockwise) {
xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
// that can be represented in binary
}
this.currentPath_.push({type: arcType,
x: aX,
y: aY,
radius: aRadius,
xStart: xStart,
yStart: yStart,
xEnd: xEnd,
yEnd: yEnd});
};
contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
};
contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
// Will destroy any existing path (same as FF behaviour)
this.beginPath();
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
this.stroke();
};
contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
// Will destroy any existing path (same as FF behaviour)
this.beginPath();
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
this.fill();
};
contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
var gradient = new CanvasGradient_("gradient");
return gradient;
};
contextPrototype.createRadialGradient = function(aX0, aY0,
aR0, aX1,
aY1, aR1) {
var gradient = new CanvasGradient_("gradientradial");
gradient.radius1_ = aR0;
gradient.radius2_ = aR1;
gradient.focus_.x = aX0;
gradient.focus_.y = aY0;
return gradient;
};
contextPrototype.drawImage = function (image, var_args) {
var dx, dy, dw, dh, sx, sy, sw, sh;
// to find the original width we overide the width and height
var oldRuntimeWidth = image.runtimeStyle.width;
var oldRuntimeHeight = image.runtimeStyle.height;
image.runtimeStyle.width = "auto";
image.runtimeStyle.height = "auto";
// get the original size
var w = image.width;
var h = image.height;
// and remove overides
image.runtimeStyle.width = oldRuntimeWidth;
image.runtimeStyle.height = oldRuntimeHeight;
if (arguments.length == 3) {
dx = arguments[1];
dy = arguments[2];
sx = sy = 0;
sw = dw = w;
sh = dh = h;
} else if (arguments.length == 5) {
dx = arguments[1];
dy = arguments[2];
dw = arguments[3];
dh = arguments[4];
sx = sy = 0;
sw = w;
sh = h;
} else if (arguments.length == 9) {
sx = arguments[1];
sy = arguments[2];
sw = arguments[3];
sh = arguments[4];
dx = arguments[5];
dy = arguments[6];
dw = arguments[7];
dh = arguments[8];
} else {
throw "Invalid number of arguments";
}
var d = this.getCoords_(dx, dy);
var w2 = sw / 2;
var h2 = sh / 2;
var vmlStr = [];
var W = 10;
var H = 10;
// For some reason that I"ve now forgotten, using divs didn"t work
vmlStr.push(" <g_vml_:group",
" coordsize="", Z * W, ",", Z * H, """,
" coordorigin="0,0"" ,
" style="width:", W, ";height:", H, ";position:absolute;");
// If filters are necessary (rotation exists), create them
// filters are bog-slow, so only create them if abbsolutely necessary
// The following check doesn"t account for skews (which don"t exist
// in the canvas spec (yet) anyway.
if (this.m_[0][0] != 1 || this.m_[0][1]) {
var filter = [];
// Note the 12/21 reversal
filter.push("M11="", this.m_[0][0], "",",
"M12="", this.m_[1][0], "",",
"M21="", this.m_[0][1], "",",
"M22="", this.m_[1][1], "",",
"Dx="", mr(d.x / Z), "",",
"Dy="", mr(d.y / Z), """);
// Bounding box calculation (need to minimize displayed area so that
// filters don"t waste time on unused pixels.
var max = d;
var c2 = this.getCoords_(dx + dw, dy);
var c3 = this.getCoords_(dx, dy + dh);
var c4 = this.getCoords_(dx + dw, dy + dh);
max.x = Math.max(max.x, c2.x, c3.x, c4.x);
max.y = Math.max(max.y, c2.y, c3.y, c4.y);
vmlStr.push("padding:0 ", mr(max.x / Z), "px ", mr(max.y / Z),
"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
filter.join(""), ", sizingmethod="clip");")
} else {
vmlStr.push("top:", mr(d.y / Z), "px;left:", mr(d.x / Z), "px;")
}
vmlStr.push(" ">" ,
"<g_vml_:image src="", image.src, """,
" style="width:", Z * dw, ";",
" height:", Z * dh, ";"",
" cropleft="", sx / w, """,
" croptop="", sy / h, """,
" cropright="", (w - sx - sw) / w, """,
" cropbottom="", (h - sy - sh) / h, """,
" />",
"</g_vml_:group>");
this.element_.insertAdjacentHTML("BeforeEnd",
vmlStr.join(""));
};
contextPrototype.stroke = function(aFill) {
var lineStr = [];
var lineOpen = false;
var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
var color = a[0];
var opacity = a[1] * this.globalAlpha;
var W = 10;
var H = 10;
lineStr.push("<g_vml_:shape",
" fillcolor="", color, """,
" filled="", Boolean(aFill), """,
" style="position:absolute;width:", W, ";height:", H, ";"",
" coordorigin="0 0" coordsize="", Z * W, " ", Z * H, """,
" stroked="", !aFill, """,
" strokeweight="", this.lineWidth, """,
" strokecolor="", color, """,
" path="");
var newSeq = false;
var min = {x: null, y: null};
var max = {x: null, y: null};
for (var i = 0; i < this.currentPath_.length; i++) {
var p = this.currentPath_[i];
if (p.type == "moveTo") {
lineStr.push(" m ");
var c = this.getCoords_(p.x, p.y);
lineStr.push(mr(c.x), ",", mr(c.y));
} else if (p.type == "lineTo") {
lineStr.push(" l ");
var c = this.getCoords_(p.x, p.y);
lineStr.push(mr(c.x), ",", mr(c.y));
} else if (p.type == "close") {
lineStr.push(" x ");
} else if (p.type == "bezierCurveTo") {
lineStr.push(" c ");
var c = this.getCoords_(p.x, p.y);
var c1 = this.getCoords_(p.cp1x, p.cp1y);
var c2 = this.getCoords_(p.cp2x, p.cp2y);
lineStr.push(mr(c1.x), ",", mr(c1.y), ",",
mr(c2.x), ",", mr(c2.y), ",",
mr(c.x), ",", mr(c.y));
} else if (p.type == "at" || p.type == "wa") {
lineStr.push(" ", p.type, " ");
var c = this.getCoords_(p.x, p.y);
var cStart = this.getCoords_(p.xStart, p.yStart);
var cEnd = this.getCoords_(p.xEnd, p.yEnd);
lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",",
mr(c.y - this.arcScaleY_ * p.radius), " ",
mr(c.x + this.arcScaleX_ * p.radius), ",",
mr(c.y + this.arcScaleY_ * p.radius), " ",
mr(cStart.x), ",", mr(cStart.y), " ",
mr(cEnd.x), ",", mr(cEnd.y));
}
// TODO: Following is broken for curves due to
// move to proper paths.
// Figure out dimensions so we can do gradient fills
// properly
if(c) {
if (min.x == null || c.x < min.x) {
min.x = c.x;
}
if (max.x == null || c.x > max.x) {
max.x = c.x;
}
if (min.y == null || c.y < min.y) {
min.y = c.y;
}
if (max.y == null || c.y > max.y) {
max.y = c.y;
}
}
}
lineStr.push(" ">");
if (typeof this.fillStyle == "object") {
var focus = {x: "50%", y: "50%"};
var width = (max.x - min.x);
var height = (max.y - min.y);
var dimension = (width > height) ? width : height;
focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%";
var colors = [];
// inside radius (%)
if (this.fillStyle.type_ == "gradientradial") {
var inside = (this.fillStyle.radius1_ / dimension * 100);
// percentage that outside radius exceeds inside radius
var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
} else {
var inside = 0;
var expansion = 100;
}
var insidecolor = {offset: null, color: null};
var outsidecolor = {offset: null, color: null};
// We need to sort "colors" by percentage, from 0 > 100 otherwise ie
// won"t interpret it correctly
this.fillStyle.colors_.sort(function (cs1, cs2) {
return cs1.offset - cs2.offset;
});
for (var i = 0; i < this.fillStyle.colors_.length; i++) {
var fs = this.fillStyle.colors_[i];
colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");
if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
insidecolor.offset = fs.offset;
insidecolor.color = fs.color;
}
if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
outsidecolor.offset = fs.offset;
outsidecolor.color = fs.color;
}
}
colors.pop();
lineStr.push("<g_vml_:fill",
" color="", outsidecolor.color, """,
" color2="", insidecolor.color, """,
" type="", this.fillStyle.type_, """,
" focusposition="", focus.x, ", ", focus.y, """,
" colors="", colors.join(""), """,
" opacity="", opacity, "" />");
} else if (aFill) {
lineStr.push("<g_vml_:fill color="", color, "" opacity="", opacity, "" />");
} else {
lineStr.push(
"<g_vml_:stroke",
" opacity="", opacity,""",
" joinstyle="", this.lineJoin, """,
" miterlimit="", this.miterLimit, """,
" endcap="", processLineCap(this.lineCap) ,""",
" weight="", this.lineWidth, "px"",
" color="", color,"" />"
);
}
lineStr.push("</g_vml_:shape>");
this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));
this.currentPath_ = [];
};
contextPrototype.fill = function() {
this.stroke(true);
}
contextPrototype.closePath = function() {
this.currentPath_.push({type: "close"});
};
/**
* @private
*/
contextPrototype.getCoords_ = function(aX, aY) {
return {
x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2,
y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2
}
};
contextPrototype.save = function() {
var o = {};
copyState(this, o);
this.aStack_.push(o);
this.mStack_.push(this.m_);
this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
};
contextPrototype.restore = function() {
copyState(this.aStack_.pop(), this);
this.m_ = this.mStack_.pop();
};
contextPrototype.translate = function(aX, aY) {
var m1 = [
[1, 0, 0],
[0, 1, 0],
[aX, aY, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
contextPrototype.rotate = function(aRot) {
var c = mc(aRot);
var s = ms(aRot);
var m1 = [
[c, s, 0],
[-s, c, 0],
[0, 0, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
contextPrototype.scale = function(aX, aY) {
this.arcScaleX_ *= aX;
this.arcScaleY_ *= aY;
var m1 = [
[aX, 0, 0],
[0, aY, 0],
[0, 0, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
/******** STUBS ********/
contextPrototype.clip = function() {
// TODO: Implement
};
contextPrototype.arcTo = function() {
// TODO: Implement
};
contextPrototype.createPattern = function() {
return new CanvasPattern_;
};
// Gradient / Pattern Stubs
function CanvasGradient_(aType) {
this.type_ = aType;
this.radius1_ = 0;
this.radius2_ = 0;
this.colors_ = [];
this.focus_ = {x: 0, y: 0};
}
CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
aColor = processStyle(aColor);
this.colors_.push({offset: 1-aOffset, color: aColor});
};
function CanvasPattern_() {}
// set up externs
G_vmlCanvasManager = G_vmlCanvasManager_;
CanvasRenderingContext2D = CanvasRenderingContext2D_;
CanvasGradient = CanvasGradient_;
CanvasPattern = CanvasPattern_;
})();
} // if
</script>
<script>
window.onload = function() {
var ctx = document.getElementById("c").getContext("2d");
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.quadraticCurveTo(0, 0, 25, 75);
ctx.quadraticCurveTo(50, 0, 50, 50);
ctx.stroke();
};
</script>
</head>
<body>
<canvas id=c width=100 height=100></canvas>
<p>This tests that drawing quadratic curves work in the same way in different
browsers.</p>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Quadratic Curve Test</title>
<style>
body {
text-align: center
}
</style>
<!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
<script>
window.onload = function() {
var ctx = document.getElementById("c").getContext("2d");
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.quadraticCurveTo(0, 0, 25, 75);
ctx.quadraticCurveTo(50, 0, 50, 50);
ctx.stroke();
};
</script>
</head>
<body>
<canvas id=c width=100 height=100></canvas>
<p>This tests that drawing quadratic curves work in the same way in different
browsers.</p>
</body>
</html>