for this project http://biduleohm.free.fr/ledohm/ (sorry, the user interface is in french but the code is in english) I need an angular gradient but it doesn't exists in native so I've implemented it using a linear gradient on a line and I draw the lines more and more longer to form a triangle. The result is graphically OK but the speed isn't really good (1850 ms for 125 triangles). It's in the tab [Répartition], it redraws if there is a keyup event on one of the inputs, don't be afraid of the apparent slowness, I've limited to maximum one redraw every 2000 ms.
Before I used a simple linear gradient on the whole triangle (but this doesn't match the reality) and the speed was OK, it draws thousands of triangles in less than a second. This function was used :
drawFrontLightForColor : function(x, y, w, h, color) {
var x2 = x - w;
var x3 = x + w;
var gradient = Distri.frontCanvas.createLinearGradient(x2, y, x3, y);
gradient.addColorStop(0, 'rgba(' + color + ', ' + Distri.lightEdgeAlpha + ')');
gradient.addColorStop(0.5, 'rgba(' + color + ', ' + (color == Distri.lightColors.cw ? Distri.lightCenterAlphaCw : Distri.lightCenterAlphaOther) + ')');
gradient.addColorStop(1, 'rgba(' + color + ', ' + Distri.lightEdgeAlpha + ')');
Distri.frontCanvas.fillStyle = gradient;
Distri.frontCanvas.beginPath();
Distri.frontCanvas.moveTo(x, y);
Distri.frontCanvas.lineTo(x2, (y + h));
Distri.frontCanvas.lineTo(x3, (y + h));
Distri.frontCanvas.lineTo(x, y);
Distri.frontCanvas.fill();
Distri.frontCanvas.closePath();
},
Then I switched to this function :
drawFrontLightForColor : function(x, y, w, h, centerColor, edgeColor) {
var ratio = w / h;
var tmpY;
var tmpW;
var x2;
var x3;
var gradient;
Distri.frontCanvas.lineWidth = 1;
for (var tmpH = 0; tmpH < h; tmpH++) {
tmpY = y + tmpH;
tmpW = Math.round(tmpH * ratio);
x2 = x - tmpW;
x3 = x + tmpW;
gradient = Distri.frontCanvas.createLinearGradient(x2, tmpY, x3, tmpY);
gradient.addColorStop(0, edgeColor);
gradient.addColorStop(0.5, centerColor);
gradient.addColorStop(1, edgeColor);
Distri.frontCanvas.beginPath();
Distri.frontCanvas.moveTo(x2, tmpY);
Distri.frontCanvas.lineTo(x, tmpY);
Distri.frontCanvas.lineTo(x3, tmpY);
Distri.frontCanvas.strokeStyle = gradient;
Distri.frontCanvas.stroke();
Distri.frontCanvas.closePath();
}
},
You can find the whole source here
I can't put the beginPath, stroke, closePath out of the loop because of the gradient which is changing every iteration (I've tried but it used the last gradient for every line (which, ironically, is identical to the first function...) which is understandable but not what I want).
I accept any advice (including redo the whole function and modify his caller to outsource some code) to improve the speed let's say 5x (ideally more).
I think you took the wrong way from the start : when doing so much changes of color, you have better operate at the pixel level.
So yes that could be with a webgl pixel shader, but you'll have to fight just to get the boilerplate running ok on all platform (or get a lib to do that for you).
And anyway there's a solution perfect for your need, and fast enough (a few ms) : use raw pixel data, update them one by one with the relevant function, then draw the result.
The steps to do that are :
- create a buffer same size as the canvas.
- iterate through it's pixel, keeping track of the x,y of the point.
- normalize the coordinates so they match your 'space'.
- compute the value for the normalized (x,y) out of all the data that you have.
- write a color (in my example i choose greyscale) out of that value.
- draw the whole buffer to canvas.
I did a jsfiddle, and here's the result with 4 data points :
fiddle is here :
http://jsfiddle.net/gamealchemist/KsM9c/3/
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
var width = canvas.width,
height = canvas.height;
// builds an image for the target canvas
function buildImage(targetCanvas, valueForXY, someData) {
var width = targetCanvas.width;
var height = targetCanvas.height;
var tempImg = ctx.createImageData(width, height);
var buffer = tempImg.data;
var offset = 0;
var xy = [0, 0];
function normalizeXY(xy) {
xy[0] = xy[0] / width ;
xy[1] = xy[1] / height;
}
for (var y = 0; y < height; y++)
for (var x = 0; x < width; x++, offset += 4) {
xy[0] = x; xy[1]=y;
normalizeXY(xy);
var val = Math.floor(valueForXY(xy, someData) * 255);
buffer[offset] = val;
buffer[offset + 1] = val;
buffer[offset + 2] = val;
buffer[offset + 3] = 255;
}
ctx.putImageData(tempImg, 0, 0);
}
// return normalized (0->1) value for x,y and
// provided data.
// xy is a 2 elements array
function someValueForXY(xy, someData) {
var res = 0;
for (var i = 0; i < someData.length; i++) {
var thisData = someData[i];
var dist = Math.pow(sq(thisData[0] - xy[0]) + sq(thisData[1] - xy[1]), -0.55);
localRes = 0.04 * dist;
res += localRes;
}
if (res > 1) res = 1;
return res;
}
var someData = [
[0.6, 0.2],
[0.35, 0.8],
[0.2, 0.5],
[0.6, 0.75]
];
buildImage(canvas, someValueForXY, someData);
// ------------------------
function sq(x) {
return x * x
}
In fact the GameAlchemist's solution isn't fast or I do something really wrong. I've implemented this algo only for the top view because the front view is much more complex.
For 120 lights the top view take 100-105 ms with the old code and it take 1650-1700 ms with this code (and moreover it still lacks a few things in the new code like the color for example):
drawTopLightForColor_ : function(canvasW, canvasD, rampX, rampY, rampZ, ledsArrays, color) {
function sq(x) {
return x * x;
}
var tmpImg = Distri.topCanvasCtx.createImageData(canvasW, canvasD);
var rawData = tmpImg.data;
var ledsArray = ledsArrays[color];
var len = ledsArray.length;
var i = 0;
for (var y = 0; y < canvasD; y++) {
for (var x = 0; x < canvasW; x++, i += 4) {
var intensity = 0;
for (var j = 0; j < len; j++) {
intensity += 2 * Math.pow(
sq((rampX + ledsArray[j].x) - x) +
sq((rampZ + ledsArray[j].y) - y),
-0.5
);
}
if (intensity > 1) {
intensity = 1;
}
intensity = Math.round(intensity * 255);
rawData[i] = intensity;
rawData[i + 1] = intensity;
rawData[i + 2] = intensity;
rawData[i + 3] = 255;
}
}
Distri.topCanvasCtx.putImageData(tmpImg, 0, 0);
},
Am I doing something wrong?
Related
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();
};
Please try changing
this.ticks((x + w/2), h, this.frequencyBins[i])
to
this.ticks((x + w/2), y + h, this.frequencyBins[i]),
and changing
this.needle(energy, (x + w/2), h)
to
this.needle(energy, (x + w/2), y + h).
This should work.
Am trying to calculate width and height of object i loaded into canvas. When object is not rotated i get correct left right top bottom values, but when i load rotated object in canvas then i not get correct values , so i wonder what will be the logic or math formula to do achieve it.
how am doing.
initially load image into canvas
get image data from canvas
loop through image data to get only colored pixels by using alpha check
from colored pixel array find min max xy values
var temp_ray = []; // pixel array
for (var y = 0; y < imgData.height; ++y) {
for (var x = 0; x < imgData.width; ++x) {
var index = (y * imgData.width + x) * 4;
if(imgData.data[index+3]){
var xc = (index / 4) % imgData.width;
var yc = Math.floor((index / 4) / imgData.width);
temp_ray.push([xc,yc]);
}
}
}
if(temp_ray.length > 0){
var Xind = MaxMin2darray(temp_ray,0);
var Yind = MaxMin2darray(temp_ray,1);
var W = parseFloat(Xind['max']) - parseFloat(Xind['min']);
var H = parseFloat(Yind['max']) - parseFloat(Yind['min']);
var center_x = Xind['min'] + (W/2);
var center_y = Yind['min'] + (H/2);
// find corners of object
// find *min x , min y
let top_left = temp_ray[Xind['imin']]; // min X priority , min Y // top left
// find max x , *min y
let top_right = temp_ray[Yind['imin']]; // max X, min Y priority , // top right
// find *max x , min y
let bot_right = temp_ray[Xind['imax']]; // max X priority , min Y // bottom right
// find max x , *max y
let bot_left = temp_ray[Yind['imax']]; // max X , max Y priority // bottom left
var dim = {'W':W,'H':H,'CenterX':center_x,'CenterY':center_y,'top_left':top_left,'top_right':top_right,'bot_right':bot_right,'bot_left':bot_left,'Xend':Xind['max'],'Yend':Yind['max'],'Xstart':Xind['min'],'Ystart':Yind['min'],'Xend':Xind['max'],'Yend':Yind['max']};
console.log(dim);
}
and then using min max xy value find corners of object which works with none rotated objects but not work with rotated/tilted objects.
so any idea how to solve this problem
openpnp project is achieving this through opencv, but i think in js we do not have opencv library nor am that pro of java :(.
https://github.com/openpnp/openpnp/blob/develop/src/main/java/org/openpnp/vision/pipeline/stages/DrawRotatedRects.java
jsfiddle: http://jsfiddle.net/4L13vtaj/
In some simple cases (like rectangular objects), you could try to rotate the image until you minimize the number of uncolored pixels.
So you start with your image, and for each of the possible 360°, you compute the ratio. This is not perfect, but "doable" simply in pure js.
Here's a pseudoCode that might help you:
for degree in [0,365]{
rotateOriginalImageBy(degree);
cost[degree] = NemptyPixels/NfilledPixels;
}
predictedDegree = Math.min(cost);
rotateOriginalImageBy(predictedDegree);
compute 2 dimensions;
width = largerDimension;
height = shorterDimension;
Begining of an implementation (I edited your jsfiddle):
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var rotatioDegree = 45;
var imageObject = new Image();
imageObject.onload = function() {
var canvasWidth = imageObject.width;
var canvasHeight = canvasWidth; // not useful since width==height
document.getElementById('canvas').width = canvasWidth;
document.getElementById('canvas').height = canvasWidth;
ctx.clearRect(0, 0, canvasWidth, canvasWidth);
// Move registration point to the center of the canvas
ctx.translate(canvasWidth/2, canvasWidth/2)
ctx.rotate(rotatioDegree*3.1415/180);
ctx.translate(-canvasWidth/2,-canvasWidth/2)
ctx.drawImage(imageObject,0,0);
ctx.translate(canvasWidth/2, canvasWidth/2)
ctx.rotate(-rotatioDegree*3.1415/180);
ctx.translate(-canvasWidth/2,-canvasWidth/2)
var imgData = ctx.getImageData(0, 0, canvasWidth, canvasWidth);
http://jsfiddle.net/4L13vtaj/17/
If this doesn't work, you could implement some image detection techniques (Mathematical morphology for example). But i think this is outside the scope of stackoverflow.
If you work with some approximation, you can have something like that; I hope at least it can provide to you some ideas:
// some pixels in this image are not transparent, so we add a tollerance
// you can try to remove the second condition.
const isNotEmpty = (color) => color && color < 0xffaaaaaa;
function getTop(buff, w, h) {
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
let i = y * w + x;
if (isNotEmpty(buff[i])) {
return {x, y}
}
}
}
}
function getRight(buff, w, h) {
for (let x = w; x >=0; x--) {
for (let y = 0; y < h; y++) {
let i = y * w + x;
if (isNotEmpty(buff[i])) {
return {x, y}
}
}
}
}
function getBottom(buff, w, h) {
for (let y = h; y >= 0; y--) {
for (let x = 0; x < w; x++) {
let i = y * w + x;
if (isNotEmpty(buff[i])) {
return {x, y}
}
}
}
}
function getLeft(buff, w, h) {
for (let x = 0; x < w; x++) {
for (let y = 0; y < h; y++) {
let i = y * w + x;
if (isNotEmpty(buff[i])) {
return {x, y}
}
}
}
}
async function main(imageSource) {
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const imageObject = new Image();
imageObject.src = imageSource;
await new Promise(r => imageObject.onload = r);
const w = canvas.width = imageObject.width;
const h = canvas.height = imageObject.height;
ctx.clearRect(0, 0, w, h);
ctx.drawImage(imageObject, 0, 0);
const imgData = ctx.getImageData(0, 0, w, h);
const buff = new Uint32Array(imgData.data.buffer);
const points = [
getTop(buff, w, h),
getRight(buff, w, h),
getBottom(buff, w, h),
getLeft(buff, w, h)
];
ctx.strokeStyle = "#0000ff"
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
ctx.lineTo(points[1].x, points[1].y);
ctx.lineTo(points[2].x, points[2].y);
ctx.lineTo(points[3].x, points[3].y);
ctx.closePath();
ctx.stroke();
}
main(/* image's url*/);
Here the link for testing: https://codepen.io/zer0/pen/zLxyQV
There are several problem with this approach: as said, with irregular images, it's not precise, in fact you will see the pin are making the image's bounding box a little bit smaller.
But the thing can be worse: try in the link above to use the 2nd image, that is quite irregular, and you will see.
Of course we can compensate, using also a bit more complex algorithm instead this simple one, but the question is: what the expected result for something like the 2nd image? Depends by that you can decide how to proceed.
I need to generate and store the coordinates of each point of a filled circle of say, radius 10 in Javascript.
It seems like the best way to do this would be to use the midpoint circle algorithm, but I'm not sure how to adapt it to find every point in the circle. The coordinates are going to be stored as objects in an array.
Could someone help me with the implementation?
Personally I think it would probably be faster in this case to test all pixels in the bounding box for their distance to the center. If <= r then the point is in the circle and should be pushed onto your array.
function distance(p1, p2)
{
dx = p2.x - p1.x; dx *= dx;
dy = p2.y - p1.y; dy *= dy;
return Math.sqrt( dx + dy );
}
function getPoints(x, y, r)
{
var ret = [];
for (var j=x-r; j<=x+r; j++)
for (var k=y-r; k<=y+r; k++)
if (distance({x:j,y:k},{x:x,y:y}) <= r) ret.push({x:j,y:k});
return ret;
}
You loop through all the possible points and you run the Point-In-Circle check on them.
Something like the following would suffice...
var result = [];
var d = 10;
var r = d / 2;
var rr = r*r;
for(var y=0; y<d; y++)
for(var x=0; x<d; x++)
if((x-r)*(x-r)+(y-r)*(y-r) < rr)
result.push({"x": x, "y": y});
Modifying the above algorithm to handle other (more complex) shapes/path/polygons would be difficult. For a more generic solution you could use HTML5 CANVAS. You create a canvas, get the 2d context draw all of your shapes/paths/polygons in solid black then iterate through the pixel data and find the pixels with an alpha channel greater than 0 (or 127 if you want to alleviate false positives from anti-aliasing).
var r = 5; // radius of bounding circle
//
// set up a canvas element
//
var canvas = document.createElement("canvas");
canvas.width = r*2;
canvas.height = r*2;
canvas.style.width = (r*2) + "px";
canvas.style.height = (r*2) + "px";
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#000";
//
// draw your shapes/paths/polys here
//
ctx.beginPath();
ctx.arc(r, r, r, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
//
// process the pixel data
//
var imageData = ctx.getImageData(0,0,(r*2),(r*2));
var data = imageData.data;
var result = [];
var str = "";
for(var y = 0; y<(r*2); y++) {
for(var x = 0; x<(r*2); x++) {
var pixelOffset = (y * (r*2) + x) * 4;
if(data[pixelOffset+3] > 127) {
result.push({x: x, y: y});
str += "(" + x + ", " + y + ") "; // debug
}
}
}
//
// debug/test output
//
document.body.innerHTML += str;
document.body.appendChild(canvas);
alert(result.length);
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);
I have some image data in canvas, and now I need to take the left half of the image, flip it and apply it to the right, like a mirror effect.
Example, from this:
To this:
I got this far (I have the image data ready):
ctx.drawImage(this, 0, 0, 960, 540);
var imgData = ctx.getImageData(0,0,960,540);
// loop through the data and apply mirror ??
Width & height is known. Any ideas?
Loop through the image data
If the current pixel is in the left half of the image, copy it to a position on the right:
for(var y = 0; y < height; y++) {
for(var x = 0; x < width / 2; x++) { // divide by 2 to only loop through the left half of the image.
var offset = ((width* y) + x) * 4; // Pixel origin
// Get pixel
var r = data[offset];
var g = data[offset + 1];
var b = data[offset + 2];
var a = data[offset + 3];
// Calculate how far to the right the mirrored pixel is
var mirrorOffset = (width - (x * 2)) * 4;
// Get set mirrored pixel's colours
data[offset + mirrorOffset] = r;
data[offset + 1 + mirrorOffset] = g;
data[offset + 2 + mirrorOffset] = b;
data[offset + 3 + mirrorOffset] = a;
}
}
I haven't tested this, but it should (More-or less) work, or at least give you an idea of how to do it.