I want to filter an image with a predefined filter mask in JavaScript using the HTML5 Canvas Element.
I found a solution which works fine:
//define source canvas
var srccanv = document.getElementById("src_canvas");
var ctx = srccanv.getContext("2d");
var w = srccanv.width;
var h = srccanv.height;
//just draw something into the canvas
ctx.beginPath();
ctx.fillStyle="gray";
ctx.fillRect(0,0,w,h);
ctx.lineWidth = 15;
ctx.strokeStyle = "lightgray";
ctx.moveTo(0,0);
ctx.lineTo(300,150);
ctx.stroke();
//define destination canvas
var dstcanv = document.getElementById("dst_canvas");
var dctx = dstcanv.getContext("2d");
var dstImageData = dctx.getImageData(0,0,dstcanv.width,dstcanv.height);
var dst = dstImageData.data;
//filtermask
var filtermask = [-1,-1,-1,0,0,0,1,1,1];
var side = Math.round(Math.sqrt(filtermask.length));
var halfSide = Math.floor(side/2);
var srcImageData = ctx.getImageData(0,0,w,h);
var src = srcImageData.data;
var sw = w;
var sh = h;
console.time('convolution');
// go through the destination image pixels
for (var y=1; y<h-1; y++) {
for (var x=1; x<w-1; x++) {
var sy = y;
var sx = x;
var dstOff = (y*w+x)*4;
// calculate the weighed sum of the source image pixels that
// fall under the convolution matrix
var r=0, g=0, b=0, a=0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
var scy = sy + cy - halfSide;
var scx = sx + cx - halfSide;
if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
var srcOff = (scy*sw+scx)*4;
var wt = filtermask[cy*side+cx];
r += src[srcOff] * wt;
g += src[srcOff+1] * wt;
b += src[srcOff+2] * wt;
a += src[srcOff+3];
}
}
}
dst[dstOff] = r;
dst[dstOff+1] = g;
dst[dstOff+2] = b;
dst[dstOff+3] = 255;
}
}
console.timeEnd('convolution');
dctx.putImageData(dstImageData,0,0);
<canvas id="src_canvas"></canvas>
<canvas id="dst_canvas"></canvas>
or see this fiddle: https://jsfiddle.net/w0fuxt64/20/
But... I found out that the performance in IE11 is very bad. I tested my code with latest versions of firefox (38.5.2 ESR) and Chrome (47.0) and I got some results around 10-20ms for the filtering of an 300px by 150px canvas. (See time in developer console of your browser)
Testing with IE gave me results around 280ms! which is way too long to be useful for my purposes. Does anybody have any ideas how to dramatically improve this code for IE.
Thanks in advance
Beni
Related
for my learning purposes, I'm trying to copy this example.
Everything seems to be fine, but for some reasons, canvas is not updating. Whenever the code executes, is terminated or done canvas is always 0px width 0px height. Maybe I'm doing some mistakes (for sure I should say), but I have no idea what can be wrong.
// create the network
const { Layer, Network } = window.synaptic;
var inputLayer = new Layer(2);
var hiddenLayer = new Layer(15);
var outputLayer = new Layer(3);
inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);
var myNetwork = new Network({
input: inputLayer,
hidden: [hiddenLayer],
output: outputLayer
});
var imgRef = new Image();
imgRef.src = './reference.png';
var width = imgRef.width;
var height = imgRef.height;
imgRef.setAttribute('crossOrigin', '');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var canvasd = ctx.getImageData(0 , 0, width, height)
var refCanvas = document.getElementById('canvas1');
var rctx = canvas.getContext('2d');
var refHTML = document.getElementById('reference');
rctx.drawImage(refHTML, 0, 0);
var ref = rctx.getImageData(0, 0, width, height);
imageData = ctx.getImageData(0,0,width,height);
// train the network - learn XOR
var learningRate = .1;
for (var i = 0; i < 20000; i++)
{
for (var x=0;x<width;x++){
for(var y=0;y<height;y++){
myNetwork.activate([x/width, y/height]);
myNetwork.propagate(learningRate, pixel(ref.data, x,y));
var rgb = myNetwork.activate([x/width,y/height]);
imageData.data[((height * y) + x) * 4] = (rgb[0] )* 255;
imageData.data[((height * y) + x) * 4 + 1] = (rgb[1] ) * 255;
imageData.data[((height * y) + x) * 4 + 2] = (rgb[2] ) * 255;
imageData.data[((height * y) + x) * 4 + 3] = 1;
}
}
ctx.putImageData(imageData,0,0);
}
function pixel(data,a,b){
var red = data[((height*b)+a)*4];
var green = data[((height*b)+a)*4 + 1];
var blue = data[((height*b)+a)*4 + 2]
return [red/225, green/225, blue/225]
}
Code is tested on Firefox.
On line var canvas = document.getElementById('canvas'); you are selecting a canvas element from the page. This can sometimes create a conflict between JS defined Width/Height and the CSS defined Width/Height.
try instead:
var canvas = document.createElement('canvas');
canvas.height = y;
canvas.width = x;
I am looking at this website to learn how to make a custom rotating function (simply for fun). I am running my code at khan academy because it is easy for me to practice natural simulations with code. So far I have
// I would like to try and make 3d objects
//used this as a reference http://petercollingridge.appspot.com/3D-tutorial/rotating-objects
//make nodes
var node = function(x,y){
this.x = x;
this.y = y;
};
node.prototype.draw = function(){
fill(0, 0, 0);
ellipse(this.x,this.y,5,5);
};
//make and edge
var edge = function(n_1,n_2){//this n_1, and n_2 are arbitrary names for input params
this.n_1 = n_1;
this.n_2 = n_2;
};
//draw the edge
edge.prototype.draw = function(){
fill(0, 0, 0);
line(this.n_1.x,this.n_1.y, this.n_2.x, this.n_2.y);
};
//a center would be much eaiser... I will make squares with centers and diameters instead!
var square = function(x,y,d){
this.x = x;
this.y = y;
this.d = d;
//the radius
var r = this.d/2;
//make the nodes
var n1 = new node(this.x -r ,this.y +r);
var n2 = new node(this.x -r ,this.y -r);
var n3 = new node(this.x +r ,this.y -r);
var n4 = new node(this.x +r ,this.y +r);
var nArray = [n1,n2,n3,n4];
this.nArray = nArray;
//make the edges
var e1 = new edge(n1,n2);
var e2 = new edge(n2,n3);
var e3 = new edge(n3,n4);
var e4 = new edge(n4,n1);
var eArray = [e1,e2,e3,e4];
this.eArray = eArray;
};
//make new squares
var s1 = new square(125,15,20);
var s2 = new square(185,15,20);
square.prototype.draw = function() {
//draw everything
for(var i = 0; i < this.nArray.length; i++){
this.nArray[i].draw();
}
for(var j = 0; j < this.eArray.length; j++){
this.eArray[j].draw();
}
};
square.prototype.rotate2D = function(theta){
//how much we want it to change is theta
var sin_t = sin(theta);
var cos_t = cos(theta);
//we need the original x and y, since this.x and this.y will be changed
var x = this.nArray[0].x;
var y = this.nArray[0].y;
//remember trig? x' = x * cos(beta) - y * sin(beta)
// y' = y * cos(beta) - x * sin(beta)
this.nArray[0].x = x * cos_t - y * sin_t;
this.nArray[0].y = (y * cos_t) + (x * sin_t);
text(x,200,200);
};
s2.rotate2D(-3);
s2.draw();
//draw shapes
draw = function() {
//fill(255, 255, 255);
//rect(0, 0, width, height);
s1.draw();
//s2.rotate2D(3);
//s2.draw();
};
The issue is clearly in my
square.prototype.rotate2D
function. The shape should rotate around the x and y value of the node but for some reason it seems to be rotating around 0,0. Not Sure why this is, I have spent several hours trying to figure this out. Any help is appreciated. Also I feel like my general program structure is bad and I have some unnecessary code, so let me know if there are any optimizations or a better structure I can use as well.
Finally figured it out. Somewhat close to what enhzflep said.
for (var n = 0; n < this.nArray.length; n++) {
var node = this.nArray[n];
var x = this.nArray[n].x - this.x;
var y = this.nArray[n].y - this.y;
node.x = x * cos_t - y * sin_t + this.x;
node.y = y * cos_t + x * sin_t + this.y;
}
I am making a map that renders position of game objects (Project Zomboid zombies):
As user zooms out, single dots are no longer useful. Instead, I'd like to render distribution of zombies on an area using red color gradient. I tried to loop over all zombies for every rendered pixel and color it reciprocally to the sum of squared distances to the zombies. The result:
That's way too blurry. Also the results are more influenced by the zombies that are AWAY from the points - I need to influence them more by the zombies that are CLOSE. So what this is is just math. Here's the code I used:
var h = canvas.height;
var w = canvas.width;
// To loop over more than 1 pixel (performance)
var tileSize = 10;
var halfRadius = Math.floor(tileSize/2);
var time = performance.now();
// "Squared" because we didnt unsquare it
function distanceSquared(A, B) {
return (A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y);
}
// Loop for every x,y pixel (or region of pixels)
for(var y=0; y<h; y+=tileSize) {
for(var x=0; x<w; x+=tileSize) {
// Time security - stop rendering after 1 second
if(performance.now()-time>1000) {
x=w;y=h;break;
}
// Convert relative canvas offset to absolute point on the map
var point = canvasPixeltoImagePixel(x, y);
// For every zombie add sqrt(distance from this point to zombie)
var distancesRoot = 0;
// Loop over the zombies
var zombieCoords;
for(var i=0; i<zombies_length; i++) {
// Get single zombie coordinates as {x:0, y:0}
if((coords=zombies[i].pixel)==null)
coords = zombies[i].pixel = tileToPixel(zombies[i].coordinates[0], zombies[i].coordinates[1], drawer);
// square root is a) slow and b) probably not what I want anyway
var dist = distanceSquared(coords, point);
distancesRoot+=dist;
}
// The higher the sum of distances is, the more intensive should the color be
var style = 'rgba(255,0,0,'+300000000/distancesRoot+')';
// Kill the console immediatelly
//console.log(style);
// Maybe we should sample and cache the transparency styles since there's limited ammount of colors?
ctx.fillStyle = style;
ctx.fillRect(x-halfRadius,y-halfRadius,tileSize,tileSize);
}
}
I'm pretty fine with theoretical explanation how to do it, though if you make simple canvas example with some points, what would be awesome.
This is an example of a heat map. It's basically gradient orbs over points and then ramping the opacity through a heat ramp. The more orbs cluster together the more solid the color which can be shown as an amplified region with the proper ramp.
update
I cleaned up the variables a bit and put the zeeks in an animation loop. There's an fps counter to see how it's performing. The gradient circles can be expensive. We could probably do bigger worlds if we downscale the heat map. It won't be as smooth looking but will compute a lot faster.
update 2
The heat map now has an adjustable scale and as predicted we get an increase in fps.
if (typeof app === "undefined") {
var app = {};
}
app.zeeks = 200;
app.w = 600;
app.h = 400;
app.circleSize = 50;
app.scale = 0.25;
init();
function init() {
app.can = document.getElementById('can');
app.ctx = can.getContext('2d');
app.can.height = app.h;
app.can.width = app.w;
app.radius = Math.floor(app.circleSize / 2);
app.z = genZ(app.zeeks, app.w, app.h);
app.flip = false;
// Make temporary layer once.
app.layer = document.createElement('canvas');
app.layerCtx = app.layer.getContext('2d');
app.layer.width = Math.floor(app.w * app.scale);
app.layer.height = Math.floor(app.h * app.scale);
// Make the gradient canvas once.
var sCircle = Math.floor(app.circleSize * app.scale);
app.radius = Math.floor(sCircle / 2);
app.gCan = genGradientCircle(sCircle);
app.ramp = genRamp();
// fps counter
app.frames = 0;
app.fps = "- fps";
app.fpsInterval = setInterval(calcFps, 1000);
// start animation
ani();
flicker();
}
function calcFps() {
app.fps = app.frames + " fps";
app.frames = 0;
}
// animation loop
function ani() {
app.frames++;
var ctx = app.ctx;
var w = app.w;
var h = app.h;
moveZ();
//ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "#006600";
ctx.fillRect(0, 0, w, h);
if (app.flip) {
drawZ2();
drawZ();
} else {
drawZ2();
}
ctx.fillStyle = "#FFFF00";
ctx.fillText(app.fps, 10, 10);
requestAnimationFrame(ani);
}
function flicker() {
app.flip = !app.flip;
if (app.flip) {
setTimeout(flicker, 500);
} else {
setTimeout(flicker, 5000);
}
}
function genGradientCircle(size) {
// gradient image
var gCan = document.createElement('canvas');
gCan.width = gCan.height = size;
var gCtx = gCan.getContext('2d');
var radius = Math.floor(size / 2);
var grad = gCtx.createRadialGradient(radius, radius, radius, radius, radius, 0);
grad.addColorStop(1, "rgba(255,255,255,.65)");
grad.addColorStop(0, "rgba(255,255,255,0)");
gCtx.fillStyle = grad;
gCtx.fillRect(0, 0, gCan.width, gCan.height);
return gCan;
}
function genRamp() {
// Create heat gradient
var heat = document.createElement('canvas');
var hCtx = heat.getContext('2d');
heat.width = 256;
heat.height = 5;
var linGrad = hCtx.createLinearGradient(0, 0, heat.width, heat.height);
linGrad.addColorStop(1, "rgba(255,0,0,.75)");
linGrad.addColorStop(0.5, "rgba(255,255,0,.03)");
linGrad.addColorStop(0, "rgba(255,255,0,0)");
hCtx.fillStyle = linGrad;
hCtx.fillRect(0, 0, heat.width, heat.height);
// create ramp from gradient
var ramp = [];
var imageData = hCtx.getImageData(0, 0, heat.width, 1);
var d = imageData.data;
for (var x = 0; x < heat.width; x++) {
var i = x * 4;
ramp[x] = [d[i], d[i + 1], d[i + 2], d[i + 3]];
}
return ramp;
}
function genZ(n, w, h) {
var a = [];
for (var i = 0; i < n; i++) {
a[i] = [
Math.floor(Math.random() * w),
Math.floor(Math.random() * h),
Math.floor(Math.random() * 3) - 1,
Math.floor(Math.random() * 3) - 1
];
}
return a;
}
function moveZ() {
var w = app.w
var h = app.h;
var z = app.z;
for (var i = 0; i < z.length; i++) {
var s = z[i];
s[0] += s[2];
s[1] += s[3];
if (s[0] > w || s[0] < 0) s[2] *= -1;
if (s[1] > w || s[1] < 0) s[3] *= -1;
}
}
function drawZ() {
var ctx = app.ctx;
var z = app.z;
ctx.fillStyle = "#FFFF00";
for (var i = 0; i < z.length; i++) {
ctx.fillRect(z[i][0] - 2, z[i][1] - 2, 4, 4);
}
}
function drawZ2() {
var ctx = app.ctx;
var layer = app.layer;
var layerCtx = app.layerCtx;
var gCan = app.gCan;
var z = app.z;
var radius = app.radius;
// render gradients at coords onto layer
for (var i = 0; i < z.length; i++) {
var x = Math.floor((z[i][0] * app.scale) - radius);
var y = Math.floor((z[i][1] * app.scale) - radius);
layerCtx.drawImage(gCan, x, y);
}
// adjust layer for heat ramp
var ramp = app.ramp;
// apply ramp to layer
var imageData = layerCtx.getImageData(0, 0, layer.width, layer.height);
d = imageData.data;
for (var i = 0; i < d.length; i += 4) {
if (d[i + 3] != 0) {
var c = ramp[d[i + 3]];
d[i] = c[0];
d[i + 1] = c[1];
d[i + 2] = c[2];
d[i + 3] = c[3];
}
}
layerCtx.putImageData(imageData, 0, 0);
// draw layer on world
ctx.drawImage(layer, 0, 0, layer.width, layer.height, 0, 0, app.w, app.h);
}
<canvas id="can" width="600" height="400"></canvas>
I'm trying to make each rectangle in this fiddle to move up and down so it looks like a music equalizer. I wants the bars to animate up and down, and have absolutely no idea how to do animations. Fiddles would really help :)
http://jsfiddle.net/kiransh/1jqhznt6/
HTML
<canvas id="myCanvas" height="100"> </canvas>
JavaScript
var width = 8;
var distance = 3;
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.canvas.width = window.innerWidth;
var total = ctx.canvas.width;
var max = total/2+52;
var min = total/2-60;
ctx.fillStyle = "black";
ctx.globalAlpha=0.3;
for(var x = 0; x<total; x+=12)
{
if(x<= max && x>=min)
{
var height= Math.floor(Math.random()*40)+5;
}
else
{
var height= Math.floor(Math.random()*100)+5;
}
ctx.fillRect(x,100-height,width,height);
}
I don't know if this is the effect you need, but it should help you to start.
requestAnimationFrame is used to execute draw() at each frame. Each bar is re-defined after the specified delay, and then goes down.
var width = 8;
var distance = 3;
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.canvas.width = window.innerWidth;
var total = ctx.canvas.width;
var max = total/2+52;
var min = total/2-60;
ctx.fillStyle = "black";
ctx.globalAlpha=0.3;
var bars = [];
var barsCount = Math.floor(total / 12);
var barsLastRefreshTime = null;
var barsRefreshInterval = 500;
setInterval(function() {
for(var i = 0; i<barsCount; i++) {
var x = i * 12;
if(x<= max && x>=min) {
var height= Math.floor(Math.random()*40)+5;
} else {
var height= Math.floor(Math.random()*100)+5;
}
bars[i] = height;
}
barsLastRefreshTime = (new Date()).getTime();
}, barsRefreshInterval);
function draw() {
var currentTime = (new Date()).getTime();
var timePassedRate = (barsRefreshInterval - (currentTime - barsLastRefreshTime)) / barsRefreshInterval;
ctx.clearRect(0,0,c.width,c.height);
for(var i = 0; i<bars.length; i++) {
var x = i * 12;
var height = bars[i] * timePassedRate;
ctx.fillRect(x,100-height,width,height);
}
requestAnimationFrame(draw);
}
draw();
(I can't find a way to save the fiddle ?!?)
You need to use a tick function to update your canvas. You can use requestAnimationFrame() and choose a time interval you'd like to animate on (you can determine this using Date.now()).
Then, keep a list of your bars and change their height. Then re-render it to your canvas.
Here is some quick hacky code...
var width = 8;
var distance = 3;
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.canvas.width = window.innerWidth;
var total = ctx.canvas.width;
var max = total / 2 + 52;
var min = total / 2 - 60;
ctx.fillStyle = "black";
ctx.globalAlpha = 0.3;
var lastDrawTime = Date.now();
var draw = function () {
if (Date.now() - lastDrawTime < 60) {
return webkitRequestAnimationFrame(draw);
}
ctx.clearRect(0, 0, c.width, c.height);
for (var x = 0; x < total; x += 12) {
if (x <= max && x >= min) {
var height = Math.floor(Math.random() * 40) + 5;
} else {
var height = Math.floor(Math.random() * 100) + 5;
}
ctx.fillRect(x, 100 - height, width, height);
}
lastDrawTime = Date.now();
webkitRequestAnimationFrame(draw);
};
draw();
To do animation you have to clear the canvas and redraw the rectangles in their new position at each frame, using either setInterval() or setTimeout() or requestAnimationFrame().
The best is to use requestAnimationFrame() for adapation of frames per second.
I'm currently working on a solution for drawing a standard 5-point star on the canvas using JavaScript. I'm part way there but can't figure it out entirely. I'd appreciate any tips or pointers anyone might have.
I made some changes to the code that Chris posted so it would work for me:
var alpha = (2 * Math.PI) / 10;
var radius = 12;
var starXY = [100,100]
canvasCtx.beginPath();
for(var i = 11; i != 0; i--)
{
var r = radius*(i % 2 + 1)/2;
var omega = alpha * i;
canvasCtx.lineTo((r * Math.sin(omega)) + starXY[0], (r * Math.cos(omega)) + starXY[1]);
}
canvasCtx.closePath();
canvasCtx.fillStyle = "#000";
canvasCtx.fill();
Hope it helps...
n point star, points are distributed evenly around a circle. Assume the first point is at 0,r (top), with the circle centred on 0,0, and that we can construct it from a series of triangles rotated by 2π/(2n+1):
Define a rotation function:
function rotate2D(vecArr, byRads) {
var mat = [ [Math.cos(byRads), -Math.sin(byRads)],
[Math.sin(byRads), Math.cos(byRads)] ];
var result = [];
for(var i=0; i < vecArr.length; ++i) {
result[i] = [ mat[0][0]*vecArr[i][0] + mat[0][1]*vecArr[i][1],
mat[1][0]*vecArr[i][0] + mat[1][1]*vecArr[i][1] ];
}
return result;
}
Construct a star by rotating n triangles:
function generateStarTriangles(numPoints, r) {
var triangleBase = r * Math.tan(Math.PI/numPoints);
var triangle = [ [0,r], [triangleBase/2,0], [-triangleBase/2,0], [0,r] ];
var result = [];
for(var i = 0; i < numPoints; ++i) {
result[i] = rotate2D(triangle, i*(2*Math.PI/numPoints));
}
return result;
}
Define a function to draw any given array of polygons:
function drawObj(ctx, obj, offset, flipVert) {
var sign=flipVert ? -1 : 1;
for(var objIdx=0; objIdx < obj.length; ++objIdx) {
var elem = obj[objIdx];
ctx.moveTo(elem[0][0] + offset[0], sign*elem[0][1] + offset[1]);
ctx.beginPath();
for(var vert=1; vert < elem.length; ++vert) {
ctx.lineTo(elem[vert][0] + offset[0], sign*elem[vert][1] + offset[1]);
}
ctx.fill();
}
}
Use the above to draw a 5 point star:
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext('2d');
var offset = [canvas.width/2, canvas.height/2];
ctx.fillStyle="#000000";
var penta = generateStarTriangles(5, 200);
drawObj(ctx, penta, offset, true);
See it here http://jsbin.com/oyonos/2/
This is a problem where Turtle Geometry makes things simple:
5-point star:
repeat 5 times:
fwd 100,
right 144,
fwd 100,
left 72,
You need to draw the inner bits and a complete circle is 2 * PI radians. In the example below r is the radius of the encompassing circle. Code below is from an open source project (http://github.com/CIPowell/PhyloCanvas)
var alpha = (2 * Math.PI) / 10;
// works out the angle between each vertex (5 external + 5 internal = 10)
var r_point = r * 1.75; // r_point is the radius to the external point
for(var i = 11; i != 0; i--) // or i could = 10 and you could use closePath at the end
{
var ra = i % 2 == 1 ? rb: r;
var omega = alpha * i; //omega is the angle of the current point
//cx and cy are the center point of the star.
node.canvas.lineTo(cx + (ra * Math.sin(omega)), cy + (ra * Math.cos(omega)));
}
//Store or fill.
NB: This is one of those many ways to skin a cat things, I'm sure someone else has another way of doing it. Also, the reason for the decremental loop rather than the incremental is preformance. i != 0 is more efficient than i < 10 and i-- is more efficient than i++. But performance matters a lot for my code, it might not be so crucial for yours.
I was looking for such an algorithm myself and wondered if I could invent one myself. Turned out not to be too hard. So here is a small function to create stars and polygons, with options to set the number of point, outer radius, and inner radius (the latter does only apply to stars).
function makeStar(c, s, x, y , p, o, i) {
var ct = c.getContext('2d');
var points = p || 5;
var outer_radius = o || 100;
var inner_radius = i || 40;
var start_x = x || 100;
var start_y = y || 100;
var new_outer_RAD, half_new_outer_RAD;
var RAD_distance = ( 2 * Math.PI / points);
var RAD_half_PI = Math.PI /2;
var i;
ct.moveTo(start_x, start_y);
ct.beginPath();
for (i=0; i <= points; i++) {
new_outer_RAD = (i + 1) * RAD_distance;
half_new_outer_RAD = new_outer_RAD - (RAD_distance / 2);
if (s) {
ct.lineTo(start_x + Math.round(Math.cos(half_new_outer_RAD - RAD_half_PI) * inner_radius), start_y + Math.round(Math.sin(half_new_outer_RAD - RAD_half_PI) * inner_radius));
}
ct.lineTo(start_x + Math.round(Math.cos(new_outer_RAD - RAD_half_PI) * outer_radius), start_y + Math.round(Math.sin(new_outer_RAD - RAD_half_PI) * outer_radius));
}
ct.stroke();
}
var canvas = document.getElementById('canvas');
makeStar(canvas);
makeStar(canvas, true, 120,200, 7, 110, 40);