Fast Lines:
<head>
<script type="text/javascript" src="dragon.js"></script>
<script type="text/javascript" src="LineTests.js"></script>
<script type="text/javascript" src="Canvas.js"></script>
</head>
<body>
<script>
var width = 500;
var height = 500;
var lastvalue = 0;
canvas = new Canvas(width,height);
function DoDisplay(value) {
if (value == undefined) {
value = lastvalue;
}
lastvalue = value;
canvas.Clear();
switch (value) {
case '0':
LineTest(canvas);
break;
case '1':
DrawIt(canvas);
break;
case '2':
Circle(canvas);
break;
case '3':
Dragon(canvas);
break;
default:
}
canvas.Redisplay();
return;
}
canvas.Clear();
DoDisplay('0');
canvas.Redisplay();
</script>
<p>
<select onchange="DoDisplay(this.value)">
<option value="0">Line Test</option>
<option value="1">Data Test</option>
<option value="2">Circle</option>
<option value="3">Dragon</option>
</select>
Fast Lines:
<select onchange="canvas.SetFastLines(this.value); DoDisplay()">
<option value="1">On</option>
<option value="0">Off
</select>
</body>
function Canvas(width, height, locID) {
if (width == undefined || width < 0) {
width = 300;
}
if (height == undefined || height < 0) {
height = 300;
}
var canvas = document.createElement('canvas')
canvas.height = height;
canvas.width = width;
if(locID == undefined) {
document.body.appendChild(canvas);
} else {
div = document.getElementById(locID);
if (null == div) {
document.body.appendChild(canvas);
} else {
div.appendChild(canvas);
}
}
document.body.appendChild(canvas);
this.width = width;
this.height = height;
this.clearColor = {"R":0, "G": 0, "B": 0}
this.ctx = canvas.getContext("2d");
this.fastLines = true;
this.frameBuffer = new Array(this.width);
for(var x = 0; x < this.width; x++) {
this.frameBuffer[x] = new Array(this.height);
for(var y=0; y < this.height; y++) {
this.frameBuffer[x][y] = {"R": this.clearColor.R,
"G": this.clearColor.G, "B": this.clearColor.B};
}
}
return this;
}
Canvas.prototype = {
SetClearColor: function(r,g,b) {
this.clearColor.R = r;
this.clearColor.G = g;
this.clearColor.B = b;
return;
},
Width: function() {
return this.width;
},
Height: function() {
return this.height;
},
SetFastLines: function(value) {
this.fastLines = (value == '1');
console.log("fastLines is now ", this.fastLines);
},
Clear: function() {
for(var x = 0; x < this.width; x++) {
for(var y=0; y < this.height; y++) {
this.frameBuffer[x][y].R = this.clearColor.R;
this.frameBuffer[x][y].G = this.clearColor.G;
this.frameBuffer[x][y].B = this.clearColor.B;
}
}
return;
},
Redisplay: function() {
for(var x = 0; x < this.width; x++) {
for(var y=0; y < this.height; y++) {
color = "rgb(" + this.frameBuffer[x][y].R
+ ", " + this.frameBuffer[x][y].G
+ ", " + this.frameBuffer[x][y].B +")";
this.ctx.fillStyle = color;
this.ctx.fillRect(x,y,1,1);
}
}
return;
},
SetPointReal: function(x,y,r,g,b,p) {
var R, G, B;
R = Clamp(0,255,r);
G = Clamp(0,255,g);
B = Clamp(0,255,b);
this.frameBuffer[x][y].R = Math.round(R*p);
this.frameBuffer[x][y].G = Math.round(G*p);
this.frameBuffer[x][y].B = Math.round(B*p);
},
SetPoint: function(x,y,r,g,b,p) {
if(p == undefined) {
p = 1;
}
if (x >= 0 && x < this.width && y > 0 && y <= this.height) {
y = this.height-y;
this.SetPointReal(x,y,r,g,b,p);
} else {
console.log("Set Point Error, ", x, y, r, g, b,p);
}
return;
},
Point: function(x,y, r,g,b) {
this.SetPoint(x,y,r,g,b);
},
Line: function(x1, y1, r1, g1, b1, x2, y2, r2, g2, b2) {
if (this.fastLines) {
this.BruteLine(x1, y1, r1, g1, b1, x2, y2, r2, g2, b2);
} else {
this.WuLine(x1, y1, r1, g1, b1, x2, y2, r2, g2, b2);
}
},
// adopted from wikipedia Sept 16
WuLine: function(x1, y1, r1, g1, b1, x2, y2, r2, g2, b2) {
// functions for WU
function ipart(x) {
return Math.floor(x);
}
// fractional part.
function fpart(x) {
if (x < 0) {
return 1-(x-Math.floor(x));
} else {
return x-Math.floor(x);
}
};
// reverse of the fractional part
function rfpart(x) {
return 1-fpart(x);
};
// determine the axis of major change.
var steep = (Math.abs(y2-y1) > Math.abs(x2-x1));
// cover special cases.
// horizontal line
if (x1 == x2) {
if (y1 > y2) {
tmp = y1; y1= y2; y2 = tmp;
}
for(var y = y1; y <= y2; y++) {
this.SetPoint(x1, y, r1, g1, b1);
}
return;
}
// and an anti horizontal line.
if (y1 == y2) {
if (x1 > x2) {
tmp = x1; x1 = x2; x2 = tmp;
}
for(var x = x1; x <= x2; x++) {
this.SetPoint(x, y1, r1, g1, b1);
}
return;
}
// x will be the axis of change, less code but more confusing.
// Don't like it, will change if I have time.
// swap x and y
if (steep) {
tmp = x1; x1 = y1; y1 = tmp;
tmp = x2; x2 = y2; y2 = tmp;
}
// make sure that x1 is the smaller so we can have a positive
// for loop.
// swap the two points.
if (x1 > x2) {
tmp = x1; x1 = x2; x2 = tmp;
tmp = y1; y1 = y2; y2 = tmp;
}
var dx = x2 - x1;
var dy = y2 - y1;
// note this is the major change, but it might be m or 1/m
var gradient = dy/dx;
// handle the endpoints
var xEnd = Math.round(x1);
var yEnd = y1 + gradient * (xEnd - x1);
var xGap = rfpart(x1+0.5);
xPixel1 = xEnd;
yPixel1 = ipart(yEnd);
if (steep) {
this.SetPoint(yPixel1, xPixel1,r1,g1,b1, rfpart(yEnd) *xGap);
this.SetPoint(yPixel1+1, xPixel1, r1, g1,b1, fpart(yEnd)*xGap);
} else {
this.SetPoint(xPixel1, yPixel1,r1,g1,b1,rfpart(yEnd)*xGap);
this.SetPoint(xPixel1, yPixel1+1,r1, g1,b1,fpart(yEnd)*xGap);
}
// this is the first y intersection
var y = yEnd + gradient;
xEnd = Math.round(x2);
yEnd = y2 + gradient * (xEnd - x2);
xGap = fpart(x2+0.5);
var xPixel2 = xEnd;
var yPixel2 = ipart(yEnd);
if (steep) {
this.SetPoint(yPixel2, xPixel2,r1,g1,b1,rfpart(yEnd)*xGap);
this.SetPoint(yPixel2+1, xPixel2,r1, g1,b1,fpart(yEnd)*xGap);
} else {
this.SetPoint(xPixel2, yPixel2,r1,g1,b1,rfpart(yEnd)*xGap);
this.SetPoint(xPixel2, yPixel2+1,r1, g1,b1,fpart(yEnd)*xGap);
}
var x;
if (steep) {
for (x = xPixel1 +1; x < xPixel2; x++) {
this.SetPoint(ipart(y), x,r1,g1,b1,rfpart(y));
this.SetPoint(ipart(y)+1, x,r1,g1,b1,fpart(y));
y = y + gradient;
}
} else {
for (x = xPixel1 +1; x < xPixel2; x++) {
this.SetPoint(x, ipart(y), r1,g1,b1,rfpart(y));
this.SetPoint(x, ipart(y)+1, r1,g1,b1,fpart(y));
y = y + gradient;
}
}
return;
},
BruteLine: function(x1, y1, r1, g1, b1, x2, y2, r2, g2, b2) {
var dx = x2-x1;
var dy = y2-y1;
var m = dy/dx; // may be NAN but will not be used.
var x,y,dir;
if (dx == 0 && dy == 0) {
// attempt to draw a degenerate line (a point)
this.SetPoint(x1, y1, r1, g1,b1)
} else if (dx == 0) {
// no change in x, so it is a vertical line
var sy = Math.min(y1,y2);
var ey = Math.max(y1, y2);
for(y=sy; y <= ey; y++) {
this.SetPoint(x1,y,r1,g1,b1);
}
} else if (dy == 0) {
// no change in y so it is a horizontal line.
var sx = Math.min(x1,x2);
var ex = Math.max(x1, x2);
for(x=sx; x <= ex; x++) {
this.SetPoint(x,y1,r1,g1,b1);
}
} else if (Math.abs(dx) > Math.abs(dy) ) {
if (dx < 0) {
dir = -1;
} else {
dir = 1;
}
for(x=x1; x!= x2; x+= dir) {
y = Math.round(y1+m*(x-x1));
this.SetPoint(x,y,r1,g1,b1);
}
} else {
if (dy < 0) {
dir = -1;
} else {
dir = 1;
}
for(y=y1; y != y2; y+= dir) {
x = Math.round(x1 + 1/m*(y-y1));
this.SetPoint(x,y,r1,g1,b1);
}
}
return;
},
};
function Clamp(min, max, v) {
return Math.max(min, Math.min(v,max));
}
var X=0,
Y = 1,
R = 2,
G = 3,
B = 4;
data = [
[ "line", [ 10,10,255,0,0], [20,20,0,0,255], [30,30,0,255,0]],
[ "polygon", [100,100,0,255,255], [110,100,0,255,255],
[110,110,0,255,255], [100,110,0,255,255]],
];
function DrawPolyLine(canvas, ary) {
for(var p =1; p < ary.length-1; p++) {
canvas.Line(
ary[p][X],ary[p][Y], ary[p][R],ary[p][G],ary[p][B],
ary[p+1][X],ary[p+1][Y], ary[p+1][R],ary[p+1][G],ary[p+1][B]) ;
}
return;
}
function DrawPolygon(canvas, ary) {
DrawPolyLine(canvas, ary);
var a = 1, b = ary.length-1;
canvas.Line(
ary[b][X],ary[b][Y], ary[b][R],ary[b][G],ary[b][B],
ary[a][X],ary[a][Y], ary[a][R],ary[a][G],ary[a][B]) ;
return;
}
function DrawIt(canvas) {
var shape,point;
for(shape=0; shape < data.length; shape ++) {
console.log("drawing a ", data[shape][0]);
if (data[shape][0] == "line") {
DrawPolyLine(canvas, data[shape]);
} else if (data[shape][0] == "polygon") {
DrawPolygon(canvas, data[shape]);
}
}
return;
}
function Dragon(canvas) {
DrawPolyLine(canvas, dragon[0]);
return;
}
function Circle(canvas) {
var t;
var x1,y1;
var x2,y2;
var r = Math.min(canvas.Width()/2, canvas.Height()/2)*.9;
var xc, yc;
var step = 30;
xc = Math.round(canvas.Width()/2);
yc = Math.round(canvas.Height()/2);
t = 0;
for (var R = 20; R <= r ; R+= 10) {
x1 = xc + Math.round(R * Math.cos(t*Math.PI/180));
y1 = yc + Math.round(R * Math.sin(t*Math.PI/180));
for(t=step;t<360; t+= step) {
x2 = xc + Math.round(R * Math.cos(t*Math.PI/180));
y2 = yc + Math.round(R * Math.sin(t*Math.PI/180));
canvas.Line(x1, y1, 255,255,255, x2, y2 , 255, 0,0);
x1 = x2;
y1 = y2;
}
t = 360;
x2 = xc + Math.round(R * Math.cos(t*Math.PI/180));
y2 = yc + Math.round(R * Math.sin(t*Math.PI/180));
canvas.Line(x1, y1, 255,0,0, x2, y2 , 255, 0,0);
}
canvas.Point(xc, yc, 255,0,0);
}
function LineTest(canvas) {
var xm = Math.round(canvas.Width() *.1);
var ym = Math.round(canvas.Height() *.1);
var xx = Math.round(canvas.Width() * .9);
var yx = Math.round(canvas.Height() * .9);
// draw a horizontal line left to right.
canvas.Line(xm,ym, 255,255,255, xx,ym , 0, 0, 0 );
// draw a horizontal line right to left
canvas.Line(xx,yx, 255,255,255, xm, yx, 0, 0, 0 );
// draw a vertical line top to bottom
canvas.Line (xm, yx, 255, 255, 255, xm, ym, 0,0,0);
// draw a vertical line bottom to top.
canvas.Line (xx, ym, 255, 255, 255, xx, yx, 0,0,0);
// four diagionals.
canvas.Line(xm,ym,255,255,255, xx,yx,0,0,0);
canvas.Line(xx,yx,255,255,255, xm,ym,0,0,0);
canvas.Line(xx,ym,255,255,255, xm,yx,0,0,0);
canvas.Line(xm,yx,255,255,255, xx,ym,0,0,0);
var xc = Math.round(canvas.Width()/2);
var yc = Math.round(canvas.Height()/2);;
var r = Math.min(xc, yc)*.7;
var x,y;
var t;
for(t=0;t<360;t+=10) {
x = xc + Math.round(r*Math.cos(t*Math.PI/180));
y = yc + Math.round(r*Math.sin(t*Math.PI/180));
canvas.Line(xc, yc,255,0,0, x,y,155,42,0);
}
}
dragon.js is simply data.