I am trying to create some scales measuring sound frequency for a music visualiser project. They are meant to display 4 different frequencies ( bass, lowMid, highMid and treble in a 2x2 grid pattern. I'm nearly there I have my rectangles but the needle which measures and shows the frequency itself is only iterating for the top row x and not the bottom row. I'm pretty new to JavaScript so I'm sure it could be something very simple that I'm missing.
// draw the plots to the screen
this.draw = function() {
//create an array amplitude values from the fft.
var spectrum = fourier.analyze();
//iterator for selecting frequency bin.
var currentBin = 0;
push();
fill('#f0f2d2');
//nested for loop to place plots in 2*2 grid.
for(var i = 0; i < this.plotsDown; i++) {
for(var j = 0; j < this.plotsAcross; j++) {
//calculate the size of the plots
var x = this.pad * j * 10;
var y = height/20 * i * 10;
var w = (width - this.pad) / this.plotsAcross;
var h = (height - this.pad) / this.plotsDown;
//draw a rectangle at that location and size
rect(x, y, w, h);
//add on the ticks
this.ticks((x + w/2), h, this.frequencyBins[i])
var energy = fourier.getEnergy(this.frequencyBins[currentBin]);
//add the needle
this.needle(energy, (x + w/2), h)
currentBin++;
}
}
pop();
};
Related
I am trying to generate a Julia fractal in a canvas in javascript using math.js
Unfortunately every time the fractal is drawn on the canvas, it is rather slow and not very detailed.
Can anyone tell me if there is a specific reason this script is so slow or is it just to much to ask of a browser? (note: the mouse move part is disabled and it is still kinda slow)
I have tried raising and lowering the “bail_num” but everything above 1 makes the browser crash and everything below 0.2 makes everything black.
// Get the canvas and context
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
// Width and height of the image
var imagew = canvas.width;
var imageh = canvas.height;
// Image Data (RGBA)
var imagedata = context.createImageData(imagew, imageh);
// Pan and zoom parameters
var offsetx = -imagew/2;
var offsety = -imageh/2;
var panx = -2000;
var pany = -1000;
var zoom = 12000;
// c complexnumber
var c = math.complex(-0.310, 0.353);
// Palette array of 256 colors
var palette = [];
// The maximum number of iterations per pixel
var maxiterations = 200;
var bail_num = 1;
// Initialize the game
function init() {
//onmousemove listener
canvas.addEventListener('mousemove', onmousemove);
// Generate image
generateImage();
// Enter main loop
main(0);
}
// Main loop
function main(tframe) {
// Request animation frames
window.requestAnimationFrame(main);
// Draw the generate image
context.putImageData(imagedata, 0, 0);
}
// Generate the fractal image
function generateImage() {
// Iterate over the pixels
for (var y=0; y<imageh; y++) {
for (var x=0; x<imagew; x++) {
iterate(x, y, maxiterations);
}
}
}
// Calculate the color of a specific pixel
function iterate(x, y, maxiterations) {
// Convert the screen coordinate to a fractal coordinate
var x0 = (x + offsetx + panx) / zoom;
var y0 = (y + offsety + pany) / zoom;
var cn = math.complex(x0, y0);
// Iterate
var iterations = 0;
while (iterations < maxiterations && math.norm(math.complex(cn))< bail_num ) {
cn = math.add( math.sqrt(cn) , c);
iterations++;
}
// Get color based on the number of iterations
var color;
if (iterations == maxiterations) {
color = { r:0, g:0, b:0}; // Black
} else {
var index = Math.floor((iterations / (maxiterations)) * 255);
color = index;
}
// Apply the color
var pixelindex = (y * imagew + x) * 4;
imagedata.data[pixelindex] = color;
imagedata.data[pixelindex+1] = color;
imagedata.data[pixelindex+2] = color;
imagedata.data[pixelindex+3] = 255;
}
function onmousemove(e){
var pos = getMousePos(canvas, e);
//c = math.complex(-0.3+pos.x/imagew, 0.413-pos.y/imageh);
//console.log( 'Mouse position: ' + pos.x/imagew + ',' + pos.y/imageh );
// Generate a new image
generateImage();
}
function getMousePos(canvas, e) {
var rect = canvas.getBoundingClientRect();
return {
x: Math.round((e.clientX - rect.left)/(rect.right - rect.left)*canvas.width),
y: Math.round((e.clientY - rect.top)/(rect.bottom - rect.top)*canvas.height)
};
}
init();
The part of the code that is executed most is this piece:
while (iterations < maxiterations && math.norm(math.complex(cn))< bail_num ) {
cn = math.add( math.sqrt(cn) , c);
iterations++;
}
For the given canvas size and offsets you use, the above while body is executed 19,575,194 times. Therefore there are some obvious ways to improve performance:
somehow reduce the number of points for which the loop must be executed
somehow reduce the number of times these statements are executed per point
somehow improve these statements so they execute faster
The first idea is easy: reduce the canvas dimensions. But this is maybe not something you'd like to do.
The second idea can be achieved by reducing the value for bail_num, because then the while condition will be violated sooner (given that the norm of a complex number is always a positive real number). However, this will just result in more blackness, and gives the same visual effect as zooming out of the center of the fractal. Try for instance with 0.225: there just remains a "distant star". When bail_num is reduced too much, you wont even find the fractal anymore, as everything turns black. So to compensate you would then probably want to change your offset and zoom factors to get a closer view at the center of the fractal (which is still there, BTW!). But towards the center of the fractal, points need more iterations to get below bail_num, so in the end nothing is gained: you'll be back at square one with this method. It's not really a solution.
Another way to work along the second idea is to reduce maxiterations. However, this will reduce the resolution accordingly. It is clear that you will have fewer colors at your disposal, as this number directly corresponds to the number of iterations you can have at the most.
The third idea means that you would somehow optimise the calculations with complex numbers. It turns out to give a lot of gain:
Use efficient calculations
The norm that is calculated in the while condition could be used as an intermediate value for calculating the square root of the same number, which is needed in the next statement. This is the formula for getting the square root from a complex number, if you already have its norm:
__________________
root.re = √ ½(cn.re + norm)
root.im = ½cn.im/root.re
Where the re and im properties denote the real and imaginary components of the respective complex numbers. You can find the background for these formulas in this answer on math.stackexchange.
As in your code the square root is calculated separately, without taking benefit of the previous calculation of the norm, this will certainly bring a benefit.
Also, in the while condition you don't really need the norm (which involves a square root) for comparing with bail_num. You could omit the square root operation and compare with the square of bail_num, which comes down to the same thing. Obviously you would have to calculate the square of bail_num only once at the start of your code. This way you can delay that square root operation for when the condition is found true. The formula for calculating the square of the norm is as follows:
square_norm = cn.re² + cn.im²
The calls of methods on the math object have some overhead, since this library allows different types of arguments in several of its methods. So it would help performance if you would code the calculations directly without relying on math.js. The above improvements already started doing that anyway. In my attempts this also resulted in a considerable gain in performance.
Predefine colours
Although not related to the costly while loop, you can probably gain a litte bit more by calculating all possible colors (per number of iterations) at the start of the code, and store them in an array keyed by number of iterations. That way you can just perform a look-up during the actual calculations.
Some other similar things can be done to save on calculations: For instance, you could avoid translating the screen y coordinate to world coordinates while moving along the X axis, as it will always be the same value.
Here is the code that reduced the original time to complete by a factor of 10, on my PC:
Added intialisation:
// Pre-calculate the square of bail_num:
var bail_num_square = bail_num*bail_num;
// Pre-calculate the colors:
colors = [];
for (var iterations = 0; iterations <= maxiterations; iterations++) {
// Note that I have stored colours in the opposite direction to
// allow for a more efficient "countdown" loop later
colors[iterations] = 255 - Math.floor((iterations / maxiterations) * 255);
}
// Instead of using math for initialising c:
var cx = -0.310;
var cy = 0.353;
Replace functions generateImage and iterate by this one function
// Generate the fractal image
function generateImage() {
// Iterate over the pixels
var pixelindex = 0,
step = 1/zoom,
worldX, worldY,
sq, rootX, rootY, x0, y0;
for (var y=0; y<imageh; y++) {
worldY = (y + offsety + pany)/zoom;
worldX = (offsetx + panx)/zoom;
for (var x=0; x<imagew; x++) {
x0 = worldX;
y0 = worldY;
// For this point: iterate to determine color index
for (var iterations = maxiterations; iterations && (sq = (x0*x0+y0*y0)) < bail_num_square; iterations-- ) {
// root of complex number
rootX = Math.sqrt((x0 + Math.sqrt(sq))/2);
rootY = y0/(2*rootX);
x0 = rootX + cx;
y0 = rootY + cy;
}
// Apply the color
imagedata.data[pixelindex++] =
imagedata.data[pixelindex++] =
imagedata.data[pixelindex++] = colors[iterations];
imagedata.data[pixelindex++] = 255;
worldX += step;
}
}
}
With the above code you don't need to include math.js anymore.
Here is a smaller sized snippet with mouse events handled:
// Get the canvas and context
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
// Width and height of the image
var imagew = canvas.width;
var imageh = canvas.height;
// Image Data (RGBA)
var imagedata = context.createImageData(imagew, imageh);
// Pan and zoom parameters
var offsetx = -512
var offsety = -430;
var panx = -2000;
var pany = -1000;
var zoom = 12000;
// Palette array of 256 colors
var palette = [];
// The maximum number of iterations per pixel
var maxiterations = 200;
var bail_num = 0.8; //0.225; //1.15;//0.25;
// Pre-calculate the square of bail_num:
var bail_num_square = bail_num*bail_num;
// Pre-calculate the colors:
colors = [];
for (var iterations = 0; iterations <= maxiterations; iterations++) {
colors[iterations] = 255 - Math.floor((iterations / maxiterations) * 255);
}
// Instead of using math for initialising c:
var cx = -0.310;
var cy = 0.353;
// Initialize the game
function init() {
// onmousemove listener
canvas.addEventListener('mousemove', onmousemove);
// Generate image
generateImage();
// Enter main loop
main(0);
}
// Main loop
function main(tframe) {
// Request animation frames
window.requestAnimationFrame(main);
// Draw the generate image
context.putImageData(imagedata, 0, 0);
}
// Generate the fractal image
function generateImage() {
// Iterate over the pixels
console.log('generate', cx, cy);
var pixelindex = 0,
step = 1/zoom,
worldX, worldY,
sq_norm, rootX, rootY, x0, y0;
for (var y=0; y<imageh; y++) {
worldY = (y + offsety + pany)/zoom;
worldX = (offsetx + panx)/zoom;
for (var x=0; x<imagew; x++) {
x0 = worldX;
y0 = worldY;
// For this point: iterate to determine color index
for (var iterations = maxiterations; iterations && (sq_norm = (x0*x0+y0*y0)) < bail_num_square; iterations-- ) {
// root of complex number
rootX = Math.sqrt((x0 + Math.sqrt(sq_norm))/2);
rootY = y0/(2*rootX);
x0 = rootX + cx;
y0 = rootY + cy;
}
// Apply the color
imagedata.data[pixelindex++] =
imagedata.data[pixelindex++] =
imagedata.data[pixelindex++] = colors[iterations];
imagedata.data[pixelindex++] = 255;
worldX += step;
}
}
console.log(pixelindex);
}
function onmousemove(e){
var pos = getMousePos(canvas, e);
cx = -0.31+pos.x/imagew/150;
cy = 0.35-pos.y/imageh/30;
generateImage();
}
function getMousePos(canvas, e) {
var rect = canvas.getBoundingClientRect();
return {
x: Math.round((e.clientX - rect.left)/(rect.right - rect.left)*canvas.width),
y: Math.round((e.clientY - rect.top)/(rect.bottom - rect.top)*canvas.height)
};
}
init();
<canvas id="myCanvas" width="512" height="200"></canvas>
I am attempting to build a simple HTML5 canvas based image processor that takes an image and generates a tiled version of it with each tile being the average color of the underlying image area.
This is easy enough to do outside the context of a Web Worker but I'd like to use a worker so as not to block the ui processing thread. The Uint8ClampedArray form the data takes is giving me a headache with regards to how to process it tile by tile.
Below is a plunk demonstrating what I've done so far and how it's not working.
http://plnkr.co/edit/AiHmLM1lyJGztk8GHrso?p=preview
The relevant code is in worker.js
Here it is:
onmessage = function (e) {
var i,
j = 0,
k = 0,
data = e.data,
imageData = data.imageData,
tileWidth = Math.floor(data.tileWidth),
tileHeight = Math.floor(data.tileHeight),
width = imageData.width,
height = imageData.height,
tile = [],
len = imageData.data.length,
offset,
processedData = [],
tempData = [],
timesLooped = 0,
tileIncremented = 1;
function sampleTileData(tileData) {
var blockSize = 20, // only visit every x pixels
rgb = {r:0,g:0,b:0},
i = -4,
count = 0,
length = tileData.length;
while ((i += blockSize * 4) < length) {
if (tileData[i].r !== 0 && tileData[i].g !== 0 && tileData[i].b !== 0) {
++count;
rgb.r += tileData[i].r;
rgb.g += tileData[i].g;
rgb.b += tileData[i].b;
}
}
// ~~ used to floor values
rgb.r = ~~(rgb.r/count);
rgb.g = ~~(rgb.g/count);
rgb.b = ~~(rgb.b/count);
processedData.push(rgb);
}
top:
for (; j <= len; j += (width * 4) - (tileWidth * 4), timesLooped++) {
if (k === (tileWidth * 4) * tileHeight) {
k = 0;
offset = timesLooped - 1 < tileHeight ? 4 : 0;
j = ((tileWidth * 4) * tileIncremented) - offset;
timesLooped = 0;
tileIncremented++;
sampleTileData(tempData);
tempData = [];
//console.log('continue "top" loop for new tile');
continue top;
}
for (i = 0; i < tileWidth * 4; i++) {
k++;
tempData.push({r: imageData.data[j+i], g: imageData.data[j+i+1], b: imageData.data[j+i+2], a: imageData.data[j+i+3]});
}
//console.log('continue "top" loop for new row per tile');
}
postMessage(processedData);
};
I'm sure there's a better way of accomplishing what I'm trying to do starting at the labeled for loop. So any alternative methods or suggestions would be much appreciated.
Update:
I've taken a different approach to solving this:
http://jsfiddle.net/TunMn/425/
Close, but no.
I know what the problem is but I have no idea how to go about amending it. Again, any help would be much appreciated.
Approach 1: Manually calculating average per tile
Here is one approach you can try:
There is only need for reading, update can be done later using HW acceleration
Use async calls for every row (or tile if the image is very wide)
This gives an accurate result but is slower and depends on CORS restrictions.
Example
You can see the original image for a blink below. This shows the asynchronous approach works as it allows the UI to update while processing the tiles in chunks.
window.onload = function() {
var img = document.querySelector("img"),
canvas = document.querySelector("canvas"),
ctx = canvas.getContext("2d"),
w = img.naturalWidth, h = img.naturalHeight,
// store average tile colors here:
tileColors = [];
// draw in image
canvas.width = w; canvas.height = h;
ctx.drawImage(img, 0, 0);
// MAIN CALL: calculate, when done the callback function will be invoked
avgTiles(function() {console.log("done!")});
// The tiling function
function avgTiles(callback) {
var cols = 8, // number of tiles (make sure it produce integer value
rows = 8, // for tw/th below:)
tw = (w / cols)|0, // pixel width/height of each tile
th = (h / rows)|0,
x = 0, y = 0;
(function process() { // for async processing
var data, len, count, r, g, b, i;
while(x < cols) { // get next tile on x axis
r = g = b = i = 0;
data = ctx.getImageData(x * tw, y * th, tw, th).data; // single tile
len = data.length;
count = len / 4;
while(i < len) { // calc this tile's color average
r += data[i++]; // add values for each component
g += data[i++];
b += data[i++];
i++
}
// store average color to array, no need to write back at this point
tileColors.push({
r: (r / count)|0,
g: (g / count)|0,
b: (b / count)|0
});
x++; // next tile
}
y++; // next row, but do an async break below:
if (y < rows) {
x = 0;
setTimeout(process, 9); // call it async to allow browser UI to update
}
else {
// draw tiles with average colors, fillRect is faster than setting each pixel:
for(y = 0; y < rows; y++) {
for(x = 0; x < cols; x++) {
var col = tileColors[y * cols + x]; // get stored color
ctx.fillStyle = "rgb(" + col.r + "," + col.g + "," + col.b + ")";
ctx.fillRect(x * tw, y * th, tw, th);
}
}
// we're done, invoke callback
callback()
}
})(); // to self-invoke process()
}
};
<canvas></canvas>
<img src="http://i.imgur.com/X7ZrRkn.png" crossOrigin="anonymous">
Approach 2: Letting the browser do the job
We can also let the browser do the whole job exploiting interpolation and sampling.
When the browser scales an image down it will calculate the average for each new pixel. If we then turn off linear interpolation when we scale up we will get each of those average pixels as square blocks:
Scale down image at a ratio producing number of tiles as number of pixels
Turn off image smoothing
Scale the small image back up to the desired size
This will be many times faster than the first approach, and you will be able to use CORS-restricted images. Just note it may not be as accurate as the first approach, however, it is possible to increase the accuracy by scaling down the image in several step, each half the size.
Example
window.onload = function() {
var img = document.querySelector("img"),
canvas = document.querySelector("canvas"),
ctx = canvas.getContext("2d"),
w = img.naturalWidth, h = img.naturalHeight;
// draw in image
canvas.width = w; canvas.height = h;
// scale down image so number of pixels represent number of tiles,
// here use two steps so we get a more accurate result:
ctx.drawImage(img, 0, 0, w, h, 0, 0, w*0.5, h*0.5); // 50%
ctx.drawImage(canvas, 0, 0, w*0.5, h*0.5, 0, 0, 8, 8); // 8 tiles
// turn off image-smoothing
ctx.imageSmoothingEnabled =
ctx.msImageSmoothingEnabled =
ctx.mozImageSmoothingEnabled =
ctx.webkitImageSmoothingEnabled = false;
// scale image back up
ctx.drawImage(canvas, 0, 0, 8, 8, 0, 0, w, h);
};
<canvas></canvas>
<img src="http://i.imgur.com/X7ZrRkn.png" crossOrigin="anonymous">
I have a rotated rectangle that is represented by the coordinates of its 4 vertices (Actually represented by 4 lat-lon). I want to divide the rectangle into equal area grids.
I need this to find the approx area of intersection of 2 rotated rectangles, (using sampling approach).
How do I divide the rectangle into equal area grid ? I just want the center points of the grids. This is because I will check how many of these center points lie inside a 2nd rectangle and find the approx intersection area.
I tried searching, but most of the solution do not work for rotated rectangles.
Note : I am using JavaScript for implementation. Code will be helpful :)
Edit : This code is will NOT work for rotated rectangle.
var numDivide = 7;
var i = (region1[1].x - region1[0].x) / numDivide;
var j = (region1[2].y - region1[0].y) / numDivide;
var xPos = region1[0].x;
var yPos = region1[0].y;
var gridCenters = [];
for(var k = 0; k < numDivide; ++k)
{
var newPosX = (xPos + (i / 2));
for(var l = 0; l < numDivide; ++l)
{
var newPosY = (yPos + (j / 2));
gridCenters.push({x: newPosX, y: newPosY});
yPos = yPos + j;
}
xPos = xPos + i;
yPos = region1[0].y;
}
I have a set number of color palettes (8), each with 5 colors. The goal is to process an image with canvas and determine which color palette is the closest match.
Atm the minute I am getting the average RGB value from the palette then, doing the same with the source image before converting it to LAB and using CIE1976 to calculate the color difference. The closest match is the smallest distance.
This works to an extent, but many of the images I'm testing match two particular palettes. Is there a better way to calculate the most relevant palettes for an image?
So I've changed it to work with histograms. I'll put some of the code below but basically I'm:
Creating a 3D RGB histogram from the selected image, splitting rgb values into one of 8 banks, (8*8*8) so 512.
Flattening the histogram to create a single 512 array.
Normalizing the values by dividing by the total pixels in the image.
I do the same for the color palettes, creating a flat 512 histogram.
Calculate the chi-squared distance between the two histogram to find the closest color palette.
With my color palettes only having 5 colors their histogram is quite empty. Would this be an issue when comparing histograms with chi-squared.
This is how I create the flat histogram for the images to be analysed.
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
imgWidth = this.width,
imgHeight = this.height,
totalPixels = imgWidth * imgHeight;
ctx.drawImage(this, 0, 0, this.width, this.height);
var data = ctx.getImageData(0, 0, imgWidth, imgHeight).data;
var x, y, z, histogram = new Float64Array(512);
for(x=0; x<imgWidth; x++){
for(y=0; y<imgHeight; y++){
var index = imgWidth * y + x;
var rgb = [data[index], data[index+1], data[index+2] ];
// put into relevant bank
var xbin = Math.floor((rgb[0]/255)*8)
var ybin = Math.floor((rgb[1]/255)*8)
var zbin = Math.floor((rgb[2]/255)*8)
histogram[ (ybin * 8 + xbin) * 8 + zbin ] ++;
}
}
// normalize values.
for(var i=0; i<512; i++) {
histogram[i] /= totalPixels;
}
This is how I am creating the histograms for the color palettes. The colors are just stored in an array of RGB values, each palette has 5 colors.
var pals = [];
palettes.forEach(function(palette){
var paletteH = new Float64Array(512);
palette.forEach(function(color){
var xbin = Math.floor((color[0]/255)*8);
var ybin = Math.floor((color[1]/255)*8);
var zbin = Math.floor((color[2]/255)*8);
paletteH[ (ybin * 8 + xbin) * 8 + zbin ] ++;
});
for(var i=0; i<512; i++) { paletteH[i] /= 5; }
pals.push(paletteH);
});
To calculate the chi-squared distance I'm looping through each palette getting the distance to the image histogram. Then the smallest should be most similar.
for(var p = 0; p<pals.length; p++){
var result = 0;
for(var i=0; a = histogram[i], b = pals[p][i], i < 512; i++ ){
result += 0.5 * ( Math.pow(a-b,2) / (a + b + 1e-10));
}
console.log(result);
}
This works, but the results seem wrong. For example I'll analyse an image of a forest scene expecting it to result in the green color palette, but it will return another. I'd appreciate any guidance at all.
You need to use a least squares difference between your palette color and sample color.
Also you need to do this for each channel R G B and possibly A.
It would look something like this (pseudo code in [...]):
var min = 999999;
var paletteMatch;
[loop sample colors] {
[loop palette colors] {
float lsd = (Math.pow(paletteR - sampleR, 2) + [greed] + [blue]) / 3;
if (lsd < min) {
min = lsd;
paletteMatch = currentPaletteInThisLoop;
}
}
[award a point for paletteMatch for this sample Color
}
[which palette has the most points?]
I'm experimenting with some html 5 canvas and at a certain point I tried to update the canvas with new bars (rectangles).
Now the tricky part.
Lets say you have an array with 1800 items(numbers generated by a php request).
You also have an html 5 canvas with the width of 1163px.
Now you need to draw bars(rectangles) in the canvas, but the bars need to remain 2px wide and there must be a 1px margin between all bars.
So the PHP file always returns 1800 numbers (color codes from image) and you need to extract just enough numbers to fit the canvas width(with the margin included(bar+margin)).
The new array of numbers you extract cannot just be the first 100-200 numbers! It must contain the (first or second) and (last or last-1) number.
What I have tried so far
<script>
// 1800 numbers !!! ......... //
arr = [12,1,21,1,2,13,21,32,1,5,4,6,5,4,6,4,8,3,5,4,9,6,8,7,9,1,3,6,4,6,4,5,9,87,4,4,5];
c = getElementById('canvas');
ctx = c.getContex('2d');
var can = 360;
var spliter = 2;
var canWidth = c.width;
var numOfBars = arr.length;
var barwidth = 2;
var margin = 1;
// amount of bars that will fit in the canvas //
var maxbars = (canWidth / (barwidth + margin));
var offset = arr.length / maxbars;
for(var i = 0; i < arr.length; i++) {
if(arr[i] % offset){
ctx.fillStyle = "#000000";
ctx.fillRect(
this.margin + i * this.barwidth / numOfBars,
0,
barwidth + margin,
100
);
}
}
</script>
This does not seem to work how I need it to work.
Thanks in advance.
Maxbars must be an integer right? and same for offset.
After that offset is your step.
var maxbars = Math.floor(canWidth / (barwidth + margin));
var offset = Math.floor(arr.length / maxbars);
for(var i = 0; i < arr.length; i+=offset) {
var colorcode = arr[i];
// fill stuff
}