I need to extract chunks of pixels from the canvas and process them. Currently I am calling getImageData multiple times in nested loops.
_getBinaryStringFromCanvas(canvas) {
let ctx = canvas.getContext('2d')
let { xMaxBlock, yMaxBlock, blockSize } = this.metrics;
let results = '';
for (let y = 0; y < yMaxBlock; y++) {
for (let x = 0; x < xMaxBlock; x++) {
let data = ctx.getImageData(x * blockSize, y * blockSize, blockSize, blockSize);
let digit = this._somehowProcessTheData(data);
binaryString += digit;
}
}
return binaryString;
}
This is very slow as xMaxBlock and yMaxBlock can be pretty big. Ideally, I would want to do something like this -
_getChunkFromCache(cache, x, y, width, height){
// need help implementing this function
}
_getBinaryStringFromCanvas(canvas) {
let ctx = canvas.getContext('2d')
let { xMaxBlock, yMaxBlock, blockSize } = this.metrics;
let results = '';
let cache = ctx.getImageData(0, 0, xMaxBlock * blockSize, yMaxBlock * blockSize);
for (let y = 0; y < yMaxBlock; y++) {
for (let x = 0; x < xMaxBlock; x++) {
let data = this._getChunkFromCache(cache, x * blockSize, y * blockSize, blockSize, blockSize);
let digit = this._somehowProcessTheData(data);
binaryString += digit;
}
}
return binaryString;
}
But I can't seem to comprehend the logic required to address the region specified by x, y, width, height in the flat array returned
by getImageData.
Any help implementing _getChunkFromCache is highly appreciated.
Don`t copy, index the array directly.
Getting a subsection of the original data array will need to be a copy as type arrays (The array in ImageData.data is a typed array Uint8ClampedArray) are only one dimensional and can not index a 2D section of another array. Creating a copy will only add more work and increase memory usage, slowing your app even further.
Your best option is for the somehowProcessTheData function to work directly with the image data and just index the array to the bounds you supply.
function somehowProcessTheData(imageData, x, y, w, h){
var i,j;
var result = "";
var r,g,b,a;
const data = imageData.data;
for(j = 0; j < h; j++){
var idx = (x + (y + j) * imageData.width) * 4; // get left most byte index for row at y + j
for(i = 0; i < w; i++){
r = data[idx ++];
g = data[idx ++];
b = data[idx ++];
a = data[idx ++];
// do the processing
}
}
return result;
}
Or
function somehowProcessTheData(imageData, x, y, w, h){
var i,j;
var result = "";
const data = imageData.data;
for(j = 0; j < h; j++){
var idx = (x + (y + j) * imageData.width) * 4;
for(i = 0; i < w; i++){
// get the red green blue values
var blah = data[idx] + data[idx + 1] + data[idx + 2];
// do the processing
// ...
// increment the index to the next pixel.
idx += 4;
}
}
return result;
}
Then in the calling loop
let data = this.processTheData(cache, x * blockSize, y * blockSize, blockSize, blockSize);
You can cache the whole area you're working on.
It's still slow but hopefully you won't need to do it often.
Below is an example to get an int for each pixel instead of 4 bytes.
const pixels = new Int32Array(ctx.getImageData(0, 0, w, h).data.buffer)
pixels[x + y * w]
Related
Me and a friend are playing around with fractals and wanted to make an interactive website where you can change values that generate the fractal, and you can see how its affected live. On small resolution tests, the website it quite responsive but still slow.
drawFractal = () => {
for (let x = 0; x < this.canvas.width; x++) {
for (let y = 0; y < this.canvas.height; y++) {
const belongsToSet = this.checkIfBelongsToMandelbrotSet(x / this.state.magnificationFactor - this.state.panX, y / this.state.magnificationFactor - this.state.panY);
if (belongsToSet === 0) {
this.ctx.clearRect(x,y, 1,1);
} else {
this.ctx.fillStyle = `hsl(80, 100%, ${belongsToSet}%)`;
// Draw a colorful pixel
this.ctx.fillRect(x,y, 1,1);
}
}
}
}
checkIfBelongsToMandelbrotSet = (x,y) => {
let realComponentOfResult = x;
let imaginaryComponentOfResult = y;
// Set max number of iterations
for (let i = 0; i < this.state.maxIterations; i++) {
const tempRealComponent = realComponentOfResult * realComponentOfResult - imaginaryComponentOfResult * imaginaryComponentOfResult + x;
const tempImaginaryComponent = this.state.imaginaryConstant * realComponentOfResult * imaginaryComponentOfResult + y;
realComponentOfResult = tempRealComponent;
imaginaryComponentOfResult = tempImaginaryComponent;
// Return a number as a percentage
if (realComponentOfResult * imaginaryComponentOfResult > 5) {
return (i / this.state.maxIterations * 100);
}
}
// Return zero if in set
return 0;
}
This is the algorithm that handles the generation of the fractal. However we iterate over every pixel of the canvas which is quite inefficient. As a result the whole website is really slow. I wanted to ask whether it's a good idea to use html canvas or are there more efficient alternatives? Or can I optimise the drawFractal() function to be able to be more efficient? I have no idea how to continue from this point as i am inexperienced and would appreciate any feedback!
Avoid painting operations as much as you can.
When you do fillRect(x, y, 1, 1) the browser has to go from the CPU to the GPU once per pixel, and that's very inefficient.
In your case, since you are drawing every pixels on their own, you can simply set all these pixels on an ImageBitmap and put the full image once per frame.
To improve a bit the color setting, I generated an Array of a hundred values before hand, you can make it more granular if you wish.
There might be improvements to do in your Mandelbrot, I didn't checked it, but this would be more suited to CodeReview than StackOverflow.
Here is a simple demo using a 800x600px canvas:
const state = {
magnificationFactor: 5000,
imaginaryConstant: 1,
maxIterations: 20,
panX: 1,
panY: 1
};
const canvas = document.getElementById('canvas');
const width = canvas.width = 800;
const height = canvas.height = 600;
const ctx = canvas.getContext('2d');
// the ImageData on which we will draw
const img = new ImageData( width, height );
// create an Uint32 view so that we can set one pixel in one op
const img_data = new Uint32Array( img.data.buffer );
const drawFractal = () => {
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
const belongsToSet = checkIfBelongsToMandelbrotSet(x / state.magnificationFactor - state.panX, y / state.magnificationFactor - state.panY);
// setthe value in our ImageData's data
img_data[ y * width + x] = getColor( belongsToSet );
}
}
// only now we paint
ctx.putImageData( img, 0, 0 );
};
checkIfBelongsToMandelbrotSet = (x,y) => {
let realComponentOfResult = x;
let imaginaryComponentOfResult = y;
// Set max number of iterations
for (let i = 0; i < state.maxIterations; i++) {
const tempRealComponent = realComponentOfResult * realComponentOfResult - imaginaryComponentOfResult * imaginaryComponentOfResult + x;
const tempImaginaryComponent = state.imaginaryConstant * realComponentOfResult * imaginaryComponentOfResult + y;
realComponentOfResult = tempRealComponent;
imaginaryComponentOfResult = tempImaginaryComponent;
// Return a number as a percentage
if (realComponentOfResult * imaginaryComponentOfResult > 5) {
return (i / state.maxIterations * 100);
}
}
// Return zero if in set
return 0;
}
// we generate all the colors at init instead of generating every frame
const colors = Array.from( { length: 100 }, (_,i) => {
if( !i ) { return 0; }
return hslToRgb( 80/360, 100/100, i/100 );
} );
function getColor( ratio ) {
if( ratio === 0 ) { return 0; }
return colors[ Math.round( ratio ) ];
}
function anim() {
state.magnificationFactor -= 10;
drawFractal();
requestAnimationFrame( anim );
}
requestAnimationFrame( anim );
// original by mjijackson.com
// borrowed from https://stackoverflow.com/a/9493060/3702797
function hslToRgb(h, s, l){
var r, g, b;
if(s == 0){
r = g = b = l; // achromatic
}else{
var hue2rgb = function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
// we want 0xAABBGGRR format
function toHex( val ) {
return Math.round( val * 255 ).toString(16);
}
return Number( '0xFF' + toHex(b) + toHex(g) + toHex(r) );
}
<canvas id="canvas"></canvas>
I am trying to find an algorithm of how to draw a simple (no lines are allowed to intersect), irregular polygon.
The number of sides should be defined by the user, n>3.
Here is an intial code which only draws a complex polygon (lines intersect):
var ctx = document.getElementById('drawpolygon').getContext('2d');
var sides = 10;
ctx.fillStyle = '#f00';
ctx.beginPath();
ctx.moveTo(0, 0);
for(var i=0;i<sides;i++)
{
var x = getRandomInt(0, 100);
var y = getRandomInt(0, 100);
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fill();
// https://stackoverflow.com/a/1527820/1066234
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
JSFiddle: https://jsfiddle.net/kai_noack/op2La1jy/6/
I do not have any idea how to determine the next point for the connecting line, so that it does not cut any other line.
Further, the last point must close the polygon.
Here is an example of how one of the resulting polygons could look like:
Edit: I thought today that one possible algorithm would be to arrange the polygon points regular (for instance as an rectangle) and then reposition them in x-y-directions to a random amount, while checking that the generated lines are not cut.
I ported this solution to Javascript 1 to 1. Code doesn't look optimal but produces random convex(but still irregular) polygon.
//shuffle array in place
function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
/** Based on Sander Verdonschot java implementation **/
class Point {
constructor(x, y) {
this.x = x;
this.y = y
}
}
function generateRandomNumbersArray(len) {
const result = new Array(len);
for (let i = 0; i < len; ++i) {
result[i] = Math.random();
}
return result;
}
function generateRandomConvexPolygon(vertexNumber) {
const xPool = generateRandomNumbersArray(vertexNumber);
const yPool = generateRandomNumbersArray(vertexNumber);
// debugger;
xPool.sort();
yPool.sort();
const minX = xPool[0];
const maxX = xPool[xPool.length - 1];
const minY = yPool[0];
const maxY = yPool[yPool.length - 1];
const xVec = []
const yVec = [];
let lastTop = minX;
let lastBot = minX;
xPool.forEach(x => {
if (Math.random() >= 0.5) {
xVec.push(x - lastTop);
lastTop = x;
} else {
xVec.push(lastBot - x);
lastBot = x;
}
});
xVec.push(maxX - lastTop);
xVec.push(lastBot - maxX);
let lastLeft = minY;
let lastRight = minY;
yPool.forEach(y => {
if (Math.random() >= 0.5) {
yVec.push(y - lastLeft);
lastLeft = y;
} else {
yVec.push(lastRight - y);
lastRight = y;
}
});
yVec.push(maxY - lastLeft);
yVec.push(lastRight - maxY);
shuffle(yVec);
vectors = [];
for (let i = 0; i < vertexNumber; ++i) {
vectors.push(new Point(xVec[i], yVec[i]));
}
vectors.sort((v1, v2) => {
if (Math.atan2(v1.y, v1.x) > Math.atan2(v2.y, v2.x)) {
return 1;
} else {
return -1;
}
});
let x = 0, y = 0;
let minPolygonX = 0;
let minPolygonY = 0;
let points = [];
for (let i = 0; i < vertexNumber; ++i) {
points.push(new Point(x, y));
x += vectors[i].x;
y += vectors[i].y;
minPolygonX = Math.min(minPolygonX, x);
minPolygonY = Math.min(minPolygonY, y);
}
// Move the polygon to the original min and max coordinates
let xShift = minX - minPolygonX;
let yShift = minY - minPolygonY;
for (let i = 0; i < vertexNumber; i++) {
const p = points[i];
points[i] = new Point(p.x + xShift, p.y + yShift);
}
return points;
}
function draw() {
const vertices = 10;
const _points = generateRandomConvexPolygon(vertices);
//apply scale
const points = _points.map(p => new Point(p.x * 300, p.y * 300));
const ctx = document.getElementById('drawpolygon').getContext('2d');
ctx.fillStyle = '#f00';
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for(let i = 1; i < vertices ; ++i)
{
let x = points[i].x;
let y = points[i].y;
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fill();
}
draw();
<canvas id="drawpolygon"></canvas>
You could generate random points and then connect them with an approximate traveling salesman tour. Any tour that cannot be improved by 2-opt moves will not have edge crossings.
If it doesn't need to be random, here's a fast irregular n-point polygon:
Points are:
(0,0)
((i mod 2)+1, i) for 0 <= i <= n-2
Lines are between (0,0) and the two extreme points from the second row, as well as points generated by adjacent values of i.
I'm using JsIso (found it on github) to (hopefully) make a fun little browser game. I modified the hardcoded values for a height map, into a variable and function to generate terrain randomly. What I would like to do, but can't picture in my head at all, is to have a given tile no more or less than 2 levels different than the tile next to it, getting rid of towers and pits.
This is my current code:
var tileHeightMap = generateGround(10, 10); //Simple usage
function generateGround(height, width)
{
var ground = [];
for (var y = 0 ; y < height; y++)
{
ground[y] = [];
for (var x = 0; x < width; x++)
{
ground[y][x] = tile();
}
}
return ground;
function tile()
{
return (Math.random() * 5 | 0);
}
}
It looks like it would be best to modify the tile function, perhaps passing it the value of the previous tile, and not the generate ground function. If more info is needed, let me know!
You can use a two-dimensional Value Noise.
It basically works like this:
Octave #1: Create a number of random points (8, for example) that are evenly spaced in x direction and interpolate between them (if you choose linear interpolation, it could look like this):
Octave #2: Do the same thing as in #1, but double the amount of points. The amplitude should be the half of the amplitude in #1. Now interpolate again and add the values from both octaves together.
Octave #3: Do the same thing as in #2, but with the double amount of points and an amplitude that is the half of the amplitude in #2.
Continue these steps as long as you want.
This creates a one-dimensional Value Noise. The following code generates a 2d Value Noise and draws the generated map to the canvas:
function generateHeightMap (width, height, min, max) {
const heightMap = [], // 2d array containing the heights of the tiles
octaves = 4, // 4 octaves
startFrequencyX = 2,
startFrequencyY = 2;
// linear interpolation function, could also be cubic, trigonometric, ...
const interpolate = (a, b, t) => (b - a) * t + a;
let currentFrequencyX = startFrequencyX, // specifies how many points should be generated in this octave
currentFrequencyY = startFrequencyY,
currentAlpha = 1, // the amplitude
octave = 0,
x = 0,
y = 0;
// fill the height map with zeros
for (x = 0 ; x < width; x += 1) {
heightMap[x] = [];
for (y = 0; y < height; y += 1) {
heightMap[x][y] = 0;
}
}
// main loop
for (octave = 0; octave < octaves; octave += 1) {
if (octave > 0) {
currentFrequencyX *= 2; // double the amount of point
currentFrequencyY *= 2;
currentAlpha /= 2; // take the half of the amplitude
}
// create random points
const discretePoints = [];
for (x = 0; x < currentFrequencyX + 1; x += 1) {
discretePoints[x] = [];
for (y = 0; y < currentFrequencyY + 1; y += 1) {
// create a new random value between 0 and amplitude
discretePoints[x][y] = Math.random() * currentAlpha;
}
}
// now interpolate and add to the height map
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
const currentX = x / width * currentFrequencyX,
currentY = y / height * currentFrequencyY,
indexX = Math.floor(currentX),
indexY = Math.floor(currentY),
// interpolate between the 4 neighboring discrete points (2d interpolation)
w0 = interpolate(discretePoints[indexX][indexY], discretePoints[indexX + 1][indexY], currentX - indexX),
w1 = interpolate(discretePoints[indexX][indexY + 1], discretePoints[indexX + 1][indexY + 1], currentX - indexX);
// add the value to the height map
heightMap[x][y] += interpolate(w0, w1, currentY - indexY);
}
}
}
// normalize the height map
let currentMin = 2; // the highest possible value at the moment
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
if (heightMap[x][y] < currentMin) {
currentMin = heightMap[x][y];
}
}
}
// currentMin now contains the smallest value in the height map
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
heightMap[x][y] -= currentMin;
}
}
// now, the minimum value is guaranteed to be 0
let currentMax = 0;
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
if (heightMap[x][y] > currentMax) {
currentMax = heightMap[x][y];
}
}
}
// currentMax now contains the highest value in the height map
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
heightMap[x][y] /= currentMax;
}
}
// the values are now in a range from 0 to 1, modify them so that they are between min and max
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
heightMap[x][y] = heightMap[x][y] * (max - min) + min;
}
}
return heightMap;
}
const map = generateHeightMap(40, 40, 0, 2); // map size 40x40, min=0, max=2
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
for (let x = 0; x < 40; x += 1) {
for (let y = 0; y < 40; y += 1) {
const height = map[x][y];
ctx.fillStyle = 'rgb(' + height * 127 + ', 127, 127)';
// draw the tile (tile size 5x5)
ctx.fillRect(x * 5, y * 5, 5, 5);
}
}
<canvas width="200" height="200"></canvas>
Note that the values in this height map can reach from -2 to 2. To change that, change the method that is used to create the random values.
Edit:
I made a mistake there, the version before the edit reached from -1 to 1. I modified it so that you can easily specify the minimum and maximum value.
First, I normalize the height map so that the values really reach from 0 to 1. Then, I modify all values so that they are between the specified min and max value.
Also, I changed how the heights are displayed. Instead of land and water, it now displays a smooth noise. The more red a point contains, the higher it is.
By the way, this algorithm is widely used in Procedural Content Generation for games.
If you want further explanation, just ask!
I'm looping over all the pixels in an image, checking if a pixel is a certain colour then setting said pixel to yellow (r=250,g=250,b=250), else i'll set it to black (r=0,g=0,b=0).
You'll see the code to do the colour comparison below is a very primitive method, but it fits my use case just fine.
I'm currently just using a single for loop, see the code below.
var cavas = $("#canvas"),
context = canvas.getContext("2d"),
imageData = context.getImageData(),
data = imageData.data;
var r, g, b, total;
var MAX = SOME_CONST,
MIN = SOME_OTHER_CONST;
for (var i = 0, max = data.length; i < max; i +=4) {
r = data[i];
g = data[i+1];
b = data[i+2];
total = r + g + b;
if (total < MIN || total > MAX) {
//This is the colour i'm after
data[i] = 255;
data[i+1] = 255;
data[i+2] = 0;
}
else {
data[i] = 0;
data[i+1] = 0;
data[i+2] = 0;
}
}
Is there a more efficient method of performing this operation?
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);