I have made a program to make squares that produce smaller squares on the top and left that are smaller then the they where made form but running into problems controlling the variant of their sizes.
The live code can be found
jsfiddle link
main function to make the squares:
function createCubes(maxX, maxY, minX, minY,lastColor)
{
if (maxX - minX < 50 || maxY - minY < 50 )
{
return;
}
//var decayRate = .5;
var x = getNumber(minX+50, maxX-50);
var y = getNumber(minY+50, maxY-50);
var width = maxX - x;
var height = maxY - y;
var color;
do
{
color = getNumber(0, colors.length);
}
while(color == lastColor);
var tempCube = new Cube(color, x, y, width, height);
cubes.push(tempCube);
createCubes(maxX, y, x, minY,color);
createCubes(x, maxY,minX, y,color);
}
I tried increasing the min and deceasing the max values put into the getNumber function but it resulted in the squares going out of bounds.
Yes I know I called them cubes in the program.
if you need any explaining comment I will try to get to it as fast as possible.
Thanks for the Help!
Update:
I found that when subtracting the max value and setting the base chase to what I subtracted helps keep them nicer but you don't get as many.
Update:
added color and an attempt to control the squares. They still decay at an uncontrollable rate
Use requestAnimationFrame as a timing loop. rAF automatically sends a timestamp argument that you can use to control the drawing rate (decay rate) of your rectangles.
Here is annotated code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var rectSize=100;
var rectResizing=0.75;
var rectX=0;
var nextTime=0;
var decayDelay=500;
var decayRate=0.95;
var loopCount=0;
var labelY=150;
requestAnimationFrame(decayLoop);
function decayLoop(time){
// wait for elapsed time
if(time<nextTime){requestAnimationFrame(decayLoop);return;}
// reset for nextTime
nextTime=time+decayDelay;
// update the decay
decayDelay*=decayRate;
// draw the decayed rect
ctx.fillStyle='#'+Math.floor(Math.random()*16777215).toString(16);
ctx.fillRect(rectX,20,rectSize,rectSize);
rectX+=rectSize;
rectSize*=rectResizing;
// display current decayDelay & rectSize
//ctx.clearRect(0,0,cw,40);
ctx.fillStyle='black';
ctx.fillText('Loop count: '+(loopCount++)+', RectSize: '+parseInt(rectSize)+', DecayDelay: '+decayDelay,10,labelY);
labelY+=12;
// request another loop
if(rectSize>=1){
requestAnimationFrame(decayLoop);
}else{
alert('End: Rect size has decayed below 1px');
}
}
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<canvas id="canvas" width=512 height=512></canvas>
Related
I've created the grid below using a canvas as well as lines going through it, how would one go about making this grid clickable?
The ideal result would be when I click one of the boxes it turns yellow, and a value inside an array would be changed from 0 to 1. I'm fairly certain I know how to set up a 2D array but that is the general idea. Thanks for any help.
var cellCount=20;
var currentGrid = new Array(cellCount).fill().map(() => new Array(cellCount).fill(0));
var nextGrid = new Array(cellCount).fill().map(() => new Array(cellCount).fill(0));
for(var i=0;i<20;i++){
for(var j=0;j<20;j++){
currentGrid[i][j]=0;
nextGrid[i][j]=0;
}
}
var canvas=document.getElementById('grid');
var ctx=canvas.getContext('2d');
function drawGrid(h,w,id){
for(var x=0;x<w;x++){
ctx.moveTo(0,x*20);
ctx.lineTo(h,x*20);
ctx.lineWidth=1;
ctx.strokeStyle='rgb(211,211,211)';
ctx.stroke();
}
for(var y=0;y<h;y++){
ctx.moveTo(y*20,0);
ctx.lineTo(y*20,w);
ctx.lineWidth=1;
ctx.strokeStyle='rgb(211,211,211)';
ctx.stroke();
}
}
function getPosition(event)
{
if (event.x != undefined && event.y != undefined)
{
x = event.x;
y = event.y;
}
}
canvas.addEventListener("mousedown", getPosition, false);
drawGrid(421,421,'grid');
Looking at the code you provided, it appears there is no loop, meaning there will be no other updates to the grid visually. Also, the draw function appears that it would draw offscreen, as you are multiplying your values by 20, which should probably be cellCount.
First I would recommend setting up a 'game loop'. A pattern that involves updating and drawing in a loop:
function loop(tick) {
update(tick);
draw(tick);
requestAnimationFrame(loop);
}
// start the loop
requestAnimationFrame(loop);
Then you'll need to define the update and draw function. In this example you may not have anything to update yet, but you might in the future. The draw function would first erase the background and then draw the grid:
function draw(tick) {
// clear background
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw the grid
drawGrid(numCols, numRows);
}
Now you'll need to define numCols and numRows which is simply the width or height divided by the cellSize. This tells you how many cells in the height and width of the canvas. I see you have cellCount instead of cellSize, but cellSize can be calculated the opposite way by taking canvas height (or width) and dividing it by cellCount (assuming the canvas is square).
var numCols = Math.floor(canvas.width / cellSize);
var numRows = Math.floor(canvas.height / cellSize);
As for the click handler; you'll want to figure out which cell they clicked in by dividing the x and y you've captured by the cellSize.
// assuming x and y are the coordinates on the canvas where the user clicked
var cellX = Math.floor(x / cellSize);
var cellY = Math.floor(y / cellSize);
// now set the cell
nextGrid[cellY][cellX] = 1;
The following is an example code I wrote just to show I handle certain things on my game:
https://jsfiddle.net/qk7ayx7n/25/
<canvas id = "canvas"></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
JS:
var canvas = document.getElementById("canvas");
var ctx=canvas.getContext("2d");
canvas.width = 750; //keeping ratio
canvas.height = 587; //keeping ratio
$('#canvas').css("height", window.innerHeight);
$('#canvas').css("width", window.innerHeight * 1.277); //keeping the ratio
//and also resizing according to the window(not to overflow)
var board = new Image();
board.src = "https://s21.postimg.org/ko999yaaf/circ.png";
var circle = new Image();
circle.src = "https://s21.postimg.org/4zigxdh7r/circ.png";
ctx.drawImage(board, 0, 0);
var x = 10, y = 10;
ctx.drawImage(circle, x, y);
startMoving();
function startMoving(){
if(y > 310) return;
y+=3;
ctx.clearRect(0,0,750,587);
ctx.drawImage(board, 0, 0);
ctx.drawImage(circle, x, y);
setTimeout(function(){startMoving()}, 30);
}
A little explanation: This is a simple board game. first the canvas is set to the board dimensions themselves in order to get the coordinates X Y correctly(this is not useful here but in my actual game yes).
then it is resized according to the window of the player, with regards to the actual ratio of the original board image. keeping the ratio is important for the quality of the image.
Now the movement is done with a simple timer in a function, once it gets to a certain X and Y the movement is stopped.
I have trouble getting the movement of the circle to move without breaks/lags in some browsers and devices (like on an cordova app), though it works fine usually. I know that the lags are caused by the way I handle things, but why?
also, I have trouble keeping the speed of the movement constant - +3 doesn't seem to move the same in every browser.
In most cases, you should use requestAnimationFrame for JavaScript-based animations to avoid choppiness. With this technique, the position is a function of time not how many execution frames take place. This way, fast computers will have more animation frames than slow computers, but you'll still perceive the same animation velocity. For example:
var x = 10, y = 10;
var startPos = 10;
var destPos = 310;
var startTime = Date.now();
var velocity = 0.1; // pixels per millisecond
var distance = destPos - startPos;
var duration = Math.abs(distance) / velocity;
requestAnimationFrame(startMoving);
function startMoving(now) {
var elapsedTime = Math.min(now - startTime, duration);
y = startPos + (elapsedTime * velocity);
ctx.clearRect(0,0,750,587);
ctx.drawImage(board, 0, 0);
ctx.drawImage(circle, x, y);
if (elapsedTime < duration)
requestAnimationFrame(startMoving);
}
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 trying to design a traveling sine wave in JavaScript, but the design appears quite slow. The main bottleneck is the clearRect() for canvas clearing.
How can I solve this?
Also I am drawing the pixel by ctx.fillRect(x, y,1,1), but when I clear using clearRect(x, y,1,1), it leaves some footprints. Instead I have to do clearRect(x, y,5,5) to get proper clearing. What can be the work around?
/******************************/
var x = 0;
var sineval = [];
var offset = 0;
var animFlag;
function init() {
for(var i=0; i<=1000; ++i){
sineval[i] = Math.sin(i*Math.PI/180);
}
// Call the sineWave() function repeatedly every 1 microseconds
animFlag = setInterval(sineWave, 1);
//sineWave();
}
function sineWave()
{ //console.log('Drawing Sine');
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
}
for(x=0 ; x<1000 ;++x){
// Find the sine of the angle
//var i = x % 361;
var y = sineval[x+offset];
// If the sine value is positive, map it above y = 100 and change the colour to blue
if(y >= 0)
{
y = 100 - (y-0) * 70;
ctx.fillStyle = "green";
}
// If the sine value is negative, map it below y = 100 and change the colour to red
if( y < 0 )
{
y = 100 + (0-y) * 70;
ctx.fillStyle = "green";
}
// We will use the fillRect method to draw the actual wave. The length and breath of the
if(x == 0) ctx.clearRect(0,y-1,5,5);
else ctx.clearRect(x,y,5,5);
ctx.fillRect(x, y,1,1 /*Math.sin(x * Math.PI/180) * 5, Math.sin(x * Math.PI/180 * 5)*/);
}
offset = (offset > 360) ? 0 : ++offset ;
}
You need to refactor the code a bit:
Move all global variables such as canvas and context outside of the loop function
Inside the loop, clear full canvas at beginning, redraw sine
Use requestAnimationFrame instead of setInterval
Replace fillRect() with rect() and do a single fill() outside the inner for-loop
Using a timeout value of 1 ms will potentially result in blocking the browser, or at least slow it down noticeably. Considering that a monitor update only happens every 16.7ms this will of course be wasted cycles. If you want to reduce/increase the speed of the sine you can reduce/increase the incremental step instead.
In essence:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var sineval = [];
var offset = 0;
init();
function init() {
for (var i = 0; i <= 1000; ++i) {
sineval.push(Math.sin(i * Math.PI / 180));
}
// Call the sineWave() function
sineWave();
}
function sineWave() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
ctx.fillStyle = "green";
// draw positive part of sine wave here
for (var x = 0; x < 1000; x++) {
var y = sineval[x + offset];
if (y >= 0) {
y = 100 - (y - 0) * 70;
ctx.rect(x, y, 2, 2);
}
}
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "red";
// draw negative part of sine wave here
for (var x = 0; x < 1000; x++) {
var y = sineval[x + offset];
if (y < 0) {
y = 100 - (y - 0) * 70;
ctx.rect(x, y, 2, 2);
}
}
ctx.fill();
offset = (offset > 360) ? 0 : ++offset;
requestAnimationFrame(sineWave);
}
<canvas id="canvas" width=800 height=500></canvas>
And of course, if you load the script in <head> you need to wrap it in a window.onload block so canvas element is available. Or simply place the script at the bottom of the page if you haven't already.
A few speedups and odd ends:
In init, set up the sine wave pixel values one time.
Use typed arrays for these since sticking with integers is faster than using floats if possible.
We will manipulate the pixel data directly instead of using fill and clear. To start this, in init we call ctx.getImageData one time. We also just one time max the alpha value of all the pixels since the default 0 value is transparent and we want full opacity at 255.
Use setInterval like before. We want to update the pixels at a steady rate.
Use 'adj' as knob to adjust how fast the sine wave moves on the screen. The actual value (a decimal) will depend on the drawing frame rate. We use Date.now() calls to keep track of milliseconds consumed across frames. So the adjustment on the millisecond is mod 360 to set the 'offset' variable. Thus offset value is not inc by 1 every frame but instead is decided based on the consumption of time. The adj value could later be connected to gui if want.
At end of work (in sineWave function), we call requestAnimationFrame simply to do the ctx.putImageData to the canvas,screen in sync to avoid tearing. Notice 'paintit' function is fast and simple. Notice also that we still require setInterval to keep steady pace.
In between setting the offset and calling requestAnimationFrame, we do two loops. The first efficiently blackens out the exact pixels we drew from the prior frame (sets to 0). The second loop draws the new sine wave. Top half of wave is green (set the G in pixel rgba to 255). Bottom half is red (set the R pixel rgba to 255).
Use the .data array to paint a pixel, and index it to the pixel using 4x + 4y*canvas.width. Add 1 more if want the green value instead of the red one. No need to touch the blue value (byte offset 2) nor the already set alpha (byte offset 3).
The >>>0 used in some places turns the affected value into an unsigned integer if it wasn't already. It can also be used instead of Math.ceil. .data is typed Array already I think.
This answer is rather late but it addresses some issues brought up in comments or otherwise not yet addressed. The question showed up during googling.
Code hasn't been profiled. It's possible some of the speedups didn't speed anything up; however, the cpu consumption of firefox was pretty light by the end of the adjustments. It's set to run at 40 fps. Make 'delay' smaller to speed it up and tax cpu more.
var sineval;
var offset = 0;
var animFlag;
var canvas;
var ctx;
var obj;
var milli;
var delay=25;
var adj=1/delay; // .04 or so for 25 delay
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
obj=ctx.getImageData(0,0,canvas.width,canvas.height);
for (let i=0; i<obj.data.length; i+=4) {
obj.data[i+3]=255; //set all alpha to full one time only needed.
}
sineval=new Uint8Array(1400); //set up byte based table of final pixel sine values.. 1400 degrees total
for (let i=0; i<=1400; ++i) { //1400
sineval[i] = (100-70*Math.sin(i*Math.PI/180))>>>0;
}
animFlag = setInterval(sineWave, delay); //do processing once every 25 milli
milli=Date.now()>>>0; //start time in milli
}
function sineWave() {
let m=((Date.now()-milli)*adj)>>>0;
let oldoff = offset;
offset=(m % 360)>>>0; //offset,frequency tuned with adj param.
for(x=0 ; x<1000 ;++x) { //draw sine wave across canvas length of 1000
let y=sineval[x+oldoff];
obj.data [0+x*4+y*4*canvas.width]=0; //black the reds
obj.data [1+x*4+y*4*canvas.width]=0; //black the greens
}
for(x=0 ; x<1000 ;++x) { //draw sine wave across canvas length of 1000
let y=sineval[x+offset];
if (y<100) {
obj.data [1+x*4+y*4*canvas.width]=255; //rGba //green for top half
} else {
obj.data [0+x*4+y*4*canvas.width]=255; //Rgba //red for bottom half
}
}
requestAnimationFrame(paintit); //at end of processing try to paint next frame boundary
}
function paintit() {
ctx.putImageData(obj,0,0);
}
init();
<canvas id="canvas" height=300 width=1000></canvas>
I want to draw simple rectangle image to canvas. I have a four point like a;
(0) 345,223
(1) 262,191
(2) 262,107
(3) 347,77
Rendered rectangle and image are bellow;
What is the best practice to do this?
Well that was some fun. Haven't done software texture mapping in over 10 years. Nostalgia is great, but openGL is better. :D
Basically, the idea is to draw vertical slices of the image. The ctx only lets us draw images or parts of them with vertical or horizontal stretching. So, to get around this, we divide the image up into vertical slices, stretching each of them to fill a rectangle 1 pixel wide and from the top edge to the bottom edge.
First, we calculate the slope of the top and bottom edges. This corresponds to the amount that the edge rises (or falls) for each pixel travelled in the +X direction. Next, since the image may be larger or smaller than the are it will be draw onto, we must calculate how wide the strips are that correspond to 1 pixel in the X direction in the canvas.
Note, it isn't perspective-correct. Each step to the right on the canvas represents a step of the same width slice on the image - perspective correct mapping would step by varying amounts across the width of the image. Less as the image got closer, more as the image was further away from us.
Finally, it should be noted that there are a few assumptions made about the entered coordinates.
The coords appear as pairs of x and y
The coords list starts with the top-left corner
The coords must be listed in a clockwise direction
The left-edge and the right-edge must be vertical.
With these assumptions adhered to, I get the following:
Result
Code:
<!DOCTYPE html>
<html>
<head>
<script>
function byId(e){return document.getElementById(e);}
function newEl(tag){return document.createElement(tag);}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded()
{
var mImg = newEl('img');
mImg.onload = function() { stretchImage(this, quadPoints, byId('tgtCanvas') ); }
mImg.src = imgSrc;
}
var quadPoints = [ [262,107], [347,77], [347,223], [262,191] ];
var imgSrc = "img/rss128.png";
function stretchImage(srcImgElem, points, canvasElem)
{
var ctx = canvasElem.getContext('2d');
var yTopStart = points[0][1];
var yTopEnd = points[1][1];
var tgtWidth = points[1][0] - points[0][0];
var dX = tgtWidth;
var topDy = (yTopEnd-yTopStart) / dX;
var yBotStart = points[3][1];
var yBotEnd = points[2][1];
tgtWidth = points[2][0] - points[3][0];
dX = tgtWidth;
var botDy = (yBotEnd-yBotStart) / dX;
var imgW, imgH, imgDx;
imgW = srcImgElem.naturalWidth;
imgH = srcImgElem.naturalHeight;
imgDx = imgW / dX;
var curX, curYtop, curYbot, curImgX;
var i = 0;
// ctx.beginPath();
for (curX=points[0][0]; curX<points[1][0]; curX++)
{
curYtop = yTopStart + (i * topDy);
curYbot = yBotStart + (i * botDy);
curImgX = i * imgDx;
// ctx.moveTo(curX, curYtop);
// ctx.lineTo(curX, curYbot);
var sliceHeight = curYbot - curYtop;
// console.log(sliceHeight);
ctx.drawImage(srcImgElem, curImgX, 0, 1,imgH, curX, curYtop, imgDx, sliceHeight);
i++;
}
// ctx.closePath();
// ctx.stroke();
}
</script>
<style>
canvas
{
border: solid 1px black;
}
</style>
</head>
<body>
<canvas width=512 height=512 id='tgtCanvas'></canvas>
</body>
</html>
Src image: