The math behind this question has been asked numerous times, so that's not specifically what I'm after. Rather, I'm trying to program the equation for determining these points into a loop in JavaScript, so that I can display points the evenly around the circle.
So with the equations for the X and Y positions of the points:
pointX = r * cos(theta) + centerX
pointY = r * sin(theta) + centerY
I should be able to calculate it with this:
var centerX = 300;
var centerY = 175;
var radius = 100;
var numberOfPoints = 8;
var theta = 360/numberOfPoints;
for ( var i = 1; i <= numberOfPoints; i++ ) {
pointX = ( radius * Math.cos(theta * i) + centerX );
pointY = ( radius * Math.sin(theta * i) + centerY );
// Draw point ( pointX , pointY )
}
And it should give me the x,y coordinates along the perimeter for 8 points, spread 45° from each other. But this doesn't work, and I'm not understanding why.
This is the output that I get (using the HTML5 Canvas element). The points should reside on the innermost red circle, as that one has a
Incorrect:
When it "should" look like this (although this is with just 1 point, placed manually):
Correct:
Could someone help me out? It's been years since I took trig, but even with looking at other examples (from various languages), I don't see why this isn't working.
Update: Figured it out!
I didn't need to add the centerX and centerY to each calculation, because in my code, those points were already relative to the center of the circle. So, while the canvas center was at point (300, 175), all points were relative to the circle that I created (the stroke line that they need to be placed on), and so the center for them was at (0, 0). I removed this from the code, and split the theta and angle calculations into two variables for better readability, and voila!
totalPoints = 8;
for (var i = 1; i <= totalPoints ; i++) {
drawPoint(100, i, totalPoints);
}
function drawPoint(r, currentPoint, totalPoints) {
var theta = ((Math.PI*2) / totalPoints);
var angle = (theta * currentPoint);
electron.pivot.x = (r * Math.cos(angle));
electron.pivot.y = (r * Math.sin(angle));
return electron;
}
Correct:
cos and sin in Javascript accept an argument in radians, not degrees. You can change your theta calculation to
var theta = (Math.PI*2)/numberOfPoints;
See the Math.cos documentation for details
#Emmett J. Butler's solution should work. The following is a complete working example
// canvas and mousedown related variables
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
// save canvas size to vars b/ they're used often
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var centerX = 150;
var centerY = 175;
var radius = 100;
var numberOfPoints = 8;
var theta = 2.0*Math.PI/numberOfPoints;
ctx.beginPath();
for ( var i = 1; i <= numberOfPoints; i++ ) {
pointX = ( radius * Math.cos(theta * i) + centerX );
pointY = ( radius * Math.sin(theta * i) + centerY );
ctx.fillStyle = "Red";
ctx.fillRect(pointX-5,pointY-5,10,10);
ctx.fillStyle = "Green";
}
ctx.stroke();
Related
I have a code to draw spiral with points
var c = document.getElementById("myCanvas");
var cxt = c.getContext("2d");
var centerX = 400;
var centerY = 400;
cxt.moveTo(centerX, centerY);
var count = 0;
var increment = 3/32;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 4 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 4 * Math.PI * increment );
cxt.fillText("o", newX, newY);
count++;
if (count % 4 === 0) {
distance = distance + 10;
}
}
cxt.stroke();
<canvas id="myCanvas" width="800" height="800" style="border:1px solid #c3c3c3;"></canvas>
enter image description here
Notice how many time i change the increment value, there're always a line that have more or less points than others
increment = 5/32;
enter image description here
Is this possible to draw a perfect spiral with all lines has the same lenght with each other?
There are a quite a few issues here. Like #Anytech said, you need to first decide how many arms (strings of dots) you want. In your screenshot, it looks like you have 5 arms, but you probably got that by accident. I've replaced the "o" with the distance to help visualize the problem:
var c = document.getElementById("myCanvas");
var cxt = c.getContext("2d");
var centerX = 200;
var centerY = 200;
cxt.moveTo(centerX, centerY);
var count = 0;
var increment = 3/32;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 4 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 4 * Math.PI * increment );
cxt.fillText(distance, newX, newY);
count++;
if (count % 4 === 0) {
distance = distance + 10;
}
}
cxt.stroke();
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
As you can see, the first four dots are at distance 10, the 5th one is at distance 20, from there, you've already broken the rhythm.
Assuming you still want 5 arms, increase the distance every 5 dots by checking count % 5 === 0, instead of count % 4 === 0.
var c = document.getElementById("myCanvas");
var cxt = c.getContext("2d");
var centerX = 200;
var centerY = 200;
cxt.moveTo(centerX, centerY);
var count = 0;
var increment = 3/32;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 4 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 4 * Math.PI * increment );
cxt.fillText(distance, newX, newY);
count++;
if (count % 5 === 0) {
distance = distance + 10;
}
}
cxt.stroke();
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
Also, a full circle is 2 * Math.PI. If you can use Math.cos((theta) * 2 * Math.PI * increment), increment becomes the arc the dot will travel after each paint. If increment is 1/5, you will get five straight lines. If increment is a little bit more than 1/5, you will get the curve effect you want.
And one final detail, we usually call context ctx, instead of cxt. Combining all of the above, the output looks like this
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var centerX = 200;
var centerY = 200;
ctx.moveTo(centerX, centerY);
var count = 0;
var increment = 1.02/5;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 2 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 2 * Math.PI * increment );
ctx.fillText('o', newX, newY);
count++;
if (count % 5 === 0) {
distance = distance + 10;
}
}
ctx.stroke();
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
EDIT
After having a chat with the question raiser, I realize the problem also has to to with fillText using the bottom-left corner of the string as the starting point for the paint. To address the issue, we must plot actual circles, instead of letter 'o'.
Here is the final result (concentric circles added to show perfect symmetry)
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var centerX = 200;
var centerY = 200;
ctx.moveTo(centerX, centerY);
var count = 0;
var increment = 1.05/5;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 2 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 2 * Math.PI * increment );
ctx.textAlign = "center";
//ctx.fillText('o', newX, newY); <== this will be off-center
ctx.beginPath();
ctx.strokeStyle = "#000";
ctx.arc(newX, newY, 2, 0, Math.PI * 2, true)
ctx.stroke();
count++;
if (count % 5 === 0) {
ctx.strokeStyle = "#cccccc";
ctx.beginPath();
ctx.arc(200, 200, distance, 0, Math.PI * 2, true)
ctx.stroke();
distance = distance + 10;
}
}
ctx.stroke();
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
The circumference of a circle is 2 * PI * radius.
We can use this to determine what angle step to make to cover a distance on the circumference. Thus the angle that sweeps a given distance along the curve is distance / radius (Note this is not the straight line distance, but the distance ALONG the CURVE)
Though you are creating a spiral and the distance for an angle step is a little longer (as the line moves outward) the approximation will suffice for the human eye.
So changing your code with the following steps
Remove increment
Change distance to radius
Rather than limit the number of turns, we will use the radius to limit the max radius of the spiral. Adding maxRadius = 350 to fit the canvas
Add lineLength to set the approx distance between each point in pixels.
Change the for loop to a while loop as we are going to step angle inside the loop.
Rename theta to angle (we are programmers not mathematicians)
Rather than use a character to draw each point we will create a path of arcs so that the positioning can be precise. That will add some 2D context setup code.
Rather than step out every 4 points (as that no longer will work) we will make the radius a function of angle. With that add some constants to control the radius function.
radiusMin defines the min radius (starting radius)
radiusScale defines the rate per turn in pixel that the spiral moves out.
Remove count as its no longer needed
Inside the loop calculate the radius. As we have difined the radius growth as a rate per turn we divide the radiusScale / (Math.PI * 2) so the radius is radius = angle * (radiusScale / (Math.PI * 2)) + radiusMin;
Inside the loop we step the angle to match the distance we want to move angle += lineLength / radius; which is derived from the circumference formula (and why we use radians for angles)
Change cxt to the more idiomatic ctx
Add moveTo to move to the start of each circle.
Add ctx.arc to define the circle
When all circles are defined, after the loop draw the path with ctx.stroke()
The code below. As I have only approximated your spiral you can play with the constants to make it fit your needs. Also Note that for the inner spiral, longer line distances will not work as well.
const ctx = myCanvas.getContext("2d");
const centerX = 400;
const centerY = 400;
const markRadius = 2; // radius of each circle mark in pixels
const maxRadius = 350; // 50 pixel boarder
const lineLength = 20; // distance between points in pixels
const radiusScale = 80; // how fast the spiral moves outward per turn
const radiusMin = 10; // start radius
var angle = 0, radius = 0;
ctx.lineWidth = 1;
ctx.strokeStye = "black";
ctx.beginPath();
while (radius < maxRadius) {
radius = angle * (radiusScale / (Math.PI * 2)) + radiusMin;
angle += lineLength / radius;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
ctx.moveTo(x + markRadius, y);
ctx.arc(x, y, markRadius, 0, Math.PI * 2);
}
ctx.stroke();
<canvas id="myCanvas" width="800" height="800" style="border:1px solid #c3c3c3;"></canvas>
If you want separate spirals then it is a minor modification of the above
Define the number of arms in the spiral armCount
Define the rate that the arms turn as the move out "spiralRate"
Just for fun animate the spiralRate
requestAnimationFrame(mainLoop);
const ctx = myCanvas.getContext("2d");
const centerX = 200;
const centerY = 200;
const markRadius = 2; // radius of each circle mark in pixels
const maxRadius = 190; // 50 pixel boarder
const armCount = 8; // Number of arms
const radiusScale = 8; // how fast the spiral moves outward per turn
const radiusMin = 10; // start radius
function drawSpiral(spiralRate) { // spiralRate in pixels per point per turn
var angle = 0, radius = radiusMin;
ctx.lineWidth = 1;
ctx.strokeStye = "black";
ctx.beginPath();
while (radius < maxRadius) {
angle += (Math.PI * 2) / armCount + (spiralRate/ radius);
radius = angle * (radiusScale / (Math.PI * 2)) + radiusMin;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
ctx.moveTo(x + markRadius, y);
ctx.arc(x, y, markRadius, 0, Math.PI * 2);
}
ctx.stroke();
}
function mainLoop(time) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
drawSpiral(Math.sin(time / 4000) * 2); // occilate spiral rate every ~ 24 seconds
requestAnimationFrame(mainLoop);
}
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
After changing the code to increment in number form you can see that the "spiral" is not generated in the expected line.
You will ned to work out how many spiral arms you want and calculate that with the stoplimit.
The increment value is also never used.
// var increment = 6/45;
var stoplimit = 51;
https://jsfiddle.net/Anytech/spzyufev/4/
I have a code like this to draw multiple points into a circle
var cxt=c.getContext("2d");
var centerX = 500;
var centerY = 500;
cxt.moveTo(centerX, centerY);
var increment = 1/100;
var distance = 100;
for( theta=0;theta <100; theta++) {
var newX = centerX +distance *Math.cos(theta*2*Math.PI*increment);
var newY = centerY +distance *Math.sin(theta*2*Math.PI*increment);
cxt.fillText("0",newX, newY);
}
cxt.stroke();
Outcome:
enter image description here
I try to rewote the code with tan & cot (1/tan)but what i get usually an eclipse or oval. Can we use these function to make a perfect circle out of given points?
Yes, this is possible - the tan is enough (formulas here)
var cxt = c.getContext("2d");
var centerX = 100;
var centerY = 100;
cxt.moveTo(centerX, centerY);
var increment = 1 / 50;
var distance = 90;
for (let theta = 0; theta < 100; theta++) {
var t = Math.tan(theta * Math.PI * increment);
var newX = centerX + distance * (1 - t*t)/(1 + t*t);
var newY = centerY + distance * 2 * t/(1 + t*t);
cxt.fillText("0", newX, newY);
}
cxt.stroke();
<canvas id=c width=200 height=200></canvas>
I'm working on a canvas-based animation, and I'm trying to get a 3D effect in a 2D canvas.
So far, things are going well! I've got my "orbiting line of triangles" working very well:
var c = document.createElement('canvas');
c.width = c.height = 100;
document.body.appendChild(c);
var ctx = c.getContext("2d");
function Triangles() {
this.rotation = {
x: Math.random()*Math.PI*2,
y: Math.random()*Math.PI*2,
z: Math.random()*Math.PI*2
};
/* Uncomment this for testing perspective...
this.rotation = {
x: Math.PI/2,
y: 0,
z: 0
};
*/
}
Triangles.prototype.draw = function(t) {
this.rotation.z += t/1000;
var i, points;
for( i=0; i<15; i++) {
points = [
this.computeRotation(Math.cos(0.25*i),-Math.sin(0.25*i),0),
this.computeRotation(Math.cos(0.25*(i+1)),-Math.sin(0.25*(i+1)),-0.1),
this.computeRotation(Math.cos(0.25*(i+1)),-Math.sin(0.25*(i+1)),0.1)
];
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(50+40*points[0][0],50+40*points[0][1]);
ctx.lineTo(50+40*points[1][0],50+40*points[1][1]);
ctx.lineTo(50+40*points[2][0],50+40*points[2][1]);
ctx.closePath();
ctx.fill();
}
};
Triangles.prototype.computeRotation = function(x,y,z) {
var rz, ry, rx;
rz = [
Math.cos(this.rotation.z) * x - Math.sin(this.rotation.z) * y,
Math.sin(this.rotation.z) * x + Math.cos(this.rotation.z) * y,
z
];
ry = [
Math.cos(this.rotation.y) * rz[0] + Math.sin(this.rotation.y) * rz[2],
rz[1],
-Math.sin(this.rotation.y) * rz[0] + Math.cos(this.rotation.y) * rz[2]
];
rx = [
ry[0],
Math.cos(this.rotation.x) * ry[1] - Math.sin(this.rotation.x) * ry[2],
Math.sin(this.rotation.x) * ry[1] + Math.cos(this.rotation.x) * ry[2]
];
return rx;
};
var tri = new Triangles();
requestAnimationFrame(function(start) {
function step(t) {
var delta = t-start;
ctx.clearRect(0,0,100,100)
tri.draw(delta);
start = t;
requestAnimationFrame(step);
}
step(start);
});
As you can see it's using rotation matrices for calculating the position of the points after their rotation, and I'm using this to draw the triangles using the output x and y coordinates.
I want to take this a step further by using the z coordinate and adding perspective to this animation, which will make the triangles slightly bigger when in the foreground, and smaller when in the background. However, I'm not sure how to go about doing this.
I guess this is more of a maths question than a programming one, sorry about that!
Define a focal length to control the amount of perspective. The greater the value the less the amount of perspective. Then
var fl = 200; // focal length;
var px = 100; // point in 3D space
var py = 200;
var pz = 500;
Then to get the screen X,Y
var sx = (px * fl) / pz;
var sy = (py * fl) / pz;
The resulting point is relative to the center of the veiw so you need to center it to the canvas.
sx += canvas.width/2;
sy += canvas.height/2;
That is a point.
It assumes that the point being viewed is in front of the view and further than the focal length from the focal point.
I've managed to figure out a basic solution, but I'm sure there's better ones, so if you have a more complete answer feel free to add it! But for now...
Since the coordinate system is already based around the origin with the viewpoint directly on the Z axis looking at the (x,y) plane, it's actually sufficient to just multiply the (x,y) coordinates by a value proportional to z. For example, x * (z+2)/2 will do just fine in this case
There's bound to be a more proper, general solution though!
I would like to generate a canvas image using gradients in some clever way. I would like the image to looks something like this:
I just can't get my head around it. I need to generate lines in the form and arc - or use gradients with color stops in some clever way. Maybe it would be a lot easier if I converted to HSL and just go through the HUE values?
For example in a rectangle format I could
for (var i = 0; i < h; ++i) {
var ratio = i/h;
var hue = Math.floor(360*ratio);
var sat = 100;
var lum = 50;
line(dc, hslColor(hue,sat,lum), left_margin, top_margin+i, left_margin+w, top_margin+i);
}
Does anybody have any clever tips on how to produce this image using canvas?
This is not perfect (due to drawing steps ...), but it can help you :
http://jsfiddle.net/afkLY/2/
HTML:
<canvas id="colors" width="200" height="200"></canvas>
Javascript:
var canvas = document.getElementById("colors");
var graphics = canvas.getContext("2d");
var CX = canvas.width / 2,
CY = canvas.height/ 2,
sx = CX,
sy = CY;
for(var i = 0; i < 360; i+=0.1){
var rad = i * (2*Math.PI) / 360;
graphics.strokeStyle = "hsla("+i+", 100%, 50%, 1.0)";
graphics.beginPath();
graphics.moveTo(CX, CY);
graphics.lineTo(CX + sx * Math.cos(rad), CY + sy * Math.sin(rad));
graphics.stroke();
}
The idea is to draw the disc line by line with a hue value corresponding to the line direction.
You can change the color base rotation by adding a radius angle to rad variable (adding -pi/2 to rad would make the gradient look like your figure).
EDIT:
I made a new demo that generalizes the concept a bit and renders a rainbow polygon. Here is the CodePen.
To get rid of the small voids beteween the colors, I used quads that overflow to the next color part, except for the last one.
Small adjustment to make it have a white center
var canvas = document.getElementById('colorPicker');
var graphics = canvas.getContext("2d");
var CX = canvas.width / 2,
CY = canvas.height / 2,
sx = CX,
sy = CY;
for (var i = 0; i < 360; i += 0.1) {
var rad = i * (2 * Math.PI) / 360;
var grad = graphics.createLinearGradient(CX, CY, CX + sx * Math.cos(rad), CY + sy * Math.sin(rad));
grad.addColorStop(0, "white");
grad.addColorStop(0.01, "white");
grad.addColorStop(0.99, "hsla(" + i + ", 100%, 50%, 1.0)");
grad.addColorStop(1, "hsla(" + i + ", 100%, 50%, 1.0)");
graphics.strokeStyle = grad;
graphics.beginPath();
graphics.moveTo(CX, CY);
graphics.lineTo(CX + sx * Math.cos(rad), CY + sy * Math.sin(rad));
graphics.stroke();
}
Here is an alternate approach that takes a slightly more functional approach:
var canvas = document.getElementById("radial"),
ctx = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height,
center = { x: width/2, y: height/2 },
diameter = Math.min(width, height);
var distanceBetween = function(x1,y1,x2,y2) {
// Get deltas
var deltaX = x2 - x1,
deltaY = y2 - y1;
// Calculate distance from center
return Math.sqrt(deltaX*deltaX+deltaY*deltaY);
}
var angleBetween = function(x1,y1,x2,y2) {
// Get deltas
var deltaX = x2 - x1,
deltaY = y2 - y1;
// Calculate angle
return Math.atan2(deltaY, deltaX);
}
var radiansToDegrees = _.memoize(function(radians) {
// Put in range of [0,2PI)
if (radians < 0) radians += Math.PI * 2;
// convert to degrees
return radians * 180 / Math.PI;
})
// Partial application of center (x,y)
var distanceFromCenter = _.bind(distanceBetween, undefined, center.x, center.y)
var angleFromCenter = _.bind(angleBetween, undefined, center.x, center.y)
// Color formatters
var hslFormatter = function(h,s,l) { return "hsl("+h+","+s+"%,"+l+"%)"; },
fromHue = function(h) { return hslFormatter(h,100,50); };
// (x,y) => color
var getColor = function(x,y) {
// If distance is greater than radius, return black
return (distanceFromCenter(x,y) > diameter/2)
// Return black
? "#000"
// Determine color
: fromHue(radiansToDegrees(angleFromCenter(x,y)));
};
for(var y=0;y<height;y++) {
for(var x=0;x<width;x++) {
ctx.fillStyle = getColor(x,y);
ctx.fillRect( x, y, 1, 1 );
}
}
It uses a function to calculate the color at each pixel – not the most efficient implementation, but perhaps you'll glean something useful from it.
Note it uses underscore for some helper functions like bind() – for partial applications – and memoize.
Codepen for experimentation.
I have a question about Faking 3d in HTML5/Canvas/Javascript...
Basically, I have a 2d image drawn on a <canvas> using drawImage().
What I would like to do, is draw the image, then displace the texture on the sphere to look... sphere-like...
see image below for clarity:
Any ideas?
I have googled myself to near-death over this, also it cannot be WebGL as it has to work on Mobiles... is there a way to do this?
You can check a working prototype here: http://jsbin.com/ipaliq/edit
I bet there's ton's of room for optimization and better/faster algorithms, but I hope this proof of concepts will point you in the right direction.
The basic algorithm goes through each pixel of the original image and calculates its new position if it was subject to an spherical deformation.
Here's the result:
Code:
var w = 150, h = 150;
var canvas=document.getElementById("myCanvas");
var ctx=canvas.getContext("2d");
ctx.fillStyle="red";
ctx.fillRect(0,0,w,h);
//-- Init Canvas
var initCanvas = function()
{
var imgData=ctx.getImageData(0,0,w,h);
for (i=0; i<imgData.width*imgData.height*4;i+=4)
{
imgData.data[i]=i%150*1.5;
imgData.data[i+1]=i%150*1.5;
imgData.data[i+2]=(i/imgData.width)%150*1.5;
imgData.data[i+3]=255;
}
ctx.putImageData(imgData,0,0);
};
initCanvas();
var doSpherize = function()
{
var refractionIndex = 0.5; // [0..1]
//refraction index of the sphere
var radius = 75;
var radius2 = radius * radius;
var centerX = 75;
var centerY = 75;
//center of the sphere
var origX = 0;
var origY = 0;
for (x=0; x<w;x+=1)
for (y=0; y<h;y+=1)
{
var distX = x - centerX;
var distY = y - centerY;
var r2 = distX * distX + distY * distY;
origX = x;
origY = y;
if ( r2 > 0.0 && r2 < radius2 )
{
// distance
var z2 = radius2 - r2;
var z = Math.sqrt(z2);
// refraction
var xa = Math.asin( distX / Math.sqrt( distX * distX + z2 ) );
var xb = xa - xa * refractionIndex;
var ya = Math.asin( distY / Math.sqrt( distY * distY + z2 ) );
var yb = ya - ya * refractionIndex;
// displacement
origX = origX - z * Math.tan( xb );
origY = origY - z * Math.tan( yb );
}
// read
var imgData=ctx.getImageData(origX,origY,1,1);
// write
ctx.putImageData(imgData,x+w,y+h);
}
};
doSpherize();
Note
I would advice agains such an intense operation to be handed by the frontend without webGL pixel shaders. Those are very optimized and will be able to take advantage of GPU accelaration.
You can definitely do something like this. I'm not sure if the quality and speed will be good enough though, particularly on mobile.
You're going to want to getImageData the pixels, perform some mathematical transformation on them I can't think of at the moment, and then putImageData them back.
It's probably going to look kind of blurry or pixelated (you could try interpolating but that will only get you so far). And you may have to optimize your javascript considerably to get it done in a reasonable time on a mobile device.
The transformation is probably something like an inverse azimuthal projection.