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.