I am writing a "waterfall" diagram for an SDR receiver which is displayed in a canvas on a web page.
The canvas has a size of w=1000 h=800 pixels.
The top line is delivered every 50ms from a server.
The browser (using javascript) must move all lines down one line and then insert the new line at the top. This gives the look of a waterfall where all pixels are moving from top to bottom.
It is working fine, but the CPU load for the pixel moving is very high, too high for i.e. a raspberry.
What I am doing is:
var imagedata = context.getImageData(0,0,pixwidth,height-1);
var dataCopy = new Uint8ClampedArray(imagedata.data);
for(i=(dataCopy.length - (2*pixwidth*4)); i>= 0; i--) {
dataCopy[i+ pixwidth*4] = dataCopy[i];
}
imagedata.data.set(dataCopy);
// insert new top line
// ....
context.putImageData(imagedata, 0, 0);
I also tried to directly copy the pixel data in imagedata[some index],
which gives almost the same bad performance.
In another C-Program I did the same thing with a simple memcpy operation which is very fast. But what to do in Javascript ?
There are 800.000 pixels, which are 3.200.000 bytes. How can I copy or move them with the best possible performance in Javascript ?
var cv = document.getElementById('cv');
var ctx = cv.getContext('2d');
function draw() {
ctx.fillStyle = `hsla(${360 * Math.random()}, 100%, 50%, 1)`;
ctx.fillRect(0, 0, cv.width, 10);
ctx.drawImage(cv, 0, 10);
}
setInterval(function() { draw() }, 200)
<canvas id="cv" width="800" height="400"></canvas>
After drawing a line, take a snapshot of the entire canvas and redraw it with an offset of 10 px on the y scale. Repeat the process and you will get an waterfall like effect.
Related
I'm having a problem drawing sprites on canvas for a school project. My code:
treeImage = new Image();
treeImage.src = "sprites/treeSprites.png";
function rocks() { //to create the rock
this.x = 1920 * Math.random(); //random location on the width of the field
this.y = ground[Math.round(this.x/3)]; //ground is an array that stores the height of the ground
this.draw = function() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(Math.tan((ground[Math.floor(this.x/3)]-ground[Math.floor(this.x/3)+1])/-3));
//^rotating based on its position on the ground^
ctx.drawImage(treeImage, 200, 50, 50, 50, -25, -50, 50, 50);
ctx.restore();
}
}
len = rockArray.length; //every frame
for (var i = 0;i<len;i++) {
rockArray[i].draw();
}
I only request 50×50px from the image. Exactly outside of the 50×50 there are black lines (which shouldn't interfere because I only request the square within the black lines) but when I draw the rock, the black outlines are visible. (For other reasons, I can't remove the black lines.)
I'm guessing the image JavaScript stores when I load the image is made blurry, and then when I request that part from the image, the lines around are visible too, as the blur "spreads" the lines into the square I request.
Is there a way I can prevent this?
Use ctx.imageSmoothingEnabled = false.
This will make the image sharp instead of smoothed (blurry).
(documentation)
If you draw a vertical line at x=5 and width = 1, the canvas actually draws the line from 4.5 to 5.5 this results in aliasing and a fuzzy line. A quick way to remedy that so it is a solid line is to offset the entire canvas by half a pixel before doing anthing else.
ctx.translate(-0.5, -0.5);
(documentation)
For example I have a limited canvas, smaller width/height than the uploaded image in it.
Guys how to make the effect of moving the image in the canvas window? In other words, the canvas window does not change, and the picture we "run". thanks
Animation basic movement
Like all animation to make something appear as if it moves is to draw a sequence of still images (a frame), each image slightly different. If the rate of frames are high enough (over about 20 per second) the human eye and mind see the sequence of still images as a continuous movement. From the first movies to today's high end games this is how animation is done.
So for the canvas the process of drawing a frame is simple. Create a function that clears the canvas, draws what you need, exit the function so that the browser can move the completed frame to the display. (Note that while in the function anything draw on the canvas is not seen on the display, you must exit the function to see what is drawn)
To animate you must do the above at at least more than 20 times a seconds. For the best results you should do it at the same rate as the display hardware shows frames. For the browser that is always 60 frames per second (fps).
Animation in the browser
To help sync with the display you use the function requestAnimationFrame(myDrawFunction) it tells the browser that you are animating, and that the results of the rendering should be displayed only when the display hardware is ready to show a new complete frame, not when the draw function has exited (which may be halfway through a hardware frame).
Animation Object
So as a simple example let's create a animation object.
const imageSrc = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
const myImage = {
posX: 0, // current position of object
posY: 0,
speed: 3, // speed in pixels per frame (1/60th second)
direction: 0, // direction of movement in radians 0 is at 3oclock
image: (() => { // create and load an image (Image will take time to load
// and may not be ready until after the code has run.
const image = new Image;
image.src = imageSrc;
})(),
Draw function
Then a draw function that draws the object on the canvas
draw(ctx) {
ctx.drawImage(this.image, this.posX, this.posY);
},
Update function
As we are animating we need to move the object once per frame, to do this we create a update function.
update() {
this.posX += (mx = Math.cos(this.direction)) * this.speed;
this.posY += (my = Math.sin(this.direction)) * this.speed;
}
} // end of object
Many times a second
To do the animation we create a main loop that is call for every hardware display frame via requestAnimationFrame.
function mainLoop() {
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// update the obj
myImage.update();
// draw the object
myImage.draw(ctx);
// request the next frame
requestAnimationFrame(mainLoop);
// note all of the above code is not seen by the use until the
// next hardwares display frame. If you used setTimeout or setInterval
// then it would be displayed when this function exits (which may be
// halfway through a frame resulting in the animation being cut in two)
}
// request the first frame
requestAnimationFrame(mainLoop);
Some extras
And that is the most basic animation. of course you need to get the canvas context and wait for the image to load. Also because the image moves of the canvas you would need to check when it does and either stop the animation.
Eg to stop animation is image is off screen
if(myImage.posX < canvas.width){ // only render while image is on the canvas
requestAnimationFrame(mainLoop);
} else {
console.log("Animation has ended");
}
Now to put it together as a demo.
The demo
The demo has some extra smarts to make the image wrap around, ensure that the image has loaded before starting and make it start off screen, but is basicly the same as outlined above.
// get the 2D context from the canvas id
const ctx = canvas.getContext("2d");
// setup the font and text rendering
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// create the image object and load the image
const imageSrc = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
const myImage = {
posX: 0, // current position of object
posY: 0,
speed: 3, // speed in pixels per frame (1/60th second)
direction: 0, // direction of movement in radians 0 is at 3oclock
image: (() => { // create and load an image (Image will take time to load
// and may not be ready until after the code has run.
const image = new Image;
image.src = imageSrc;
// to start move the image of the display
image.onload = function(){
const imageDiagonalSize = Math.sqrt(
image.width * image.width + image.height * image.height
)
myImage.posX = (canvas.width / 2) - imageDiagonalSize - Math.cos(myImage.direction) * imageDiagonalSize;
myImage.posX = (canvas.height / 2) - imageDiagonalSize - Math.sin(myImage.direction) * imageDiagonalSize;
}
return image;
})(),
draw(ctx) {
ctx.drawImage(this.image, this.posX, this.posY);
},
update() {
var mx,my; // get movement x and y
this.posX += (mx = Math.cos(this.direction)) * this.speed;
this.posY += (my = Math.sin(this.direction)) * this.speed;
// if the image moves of the screen move it to the other side
if(mx > 0) { // if moving right
if(this.posX > canvas.width){
this.posX = 0-this.image.width;
}
}else if(mx < 0) { // if moving left
if(this.posX + this.image.width < 0){
this.posX = canvas.width;
}
}
if(my > 0) { // if moving down
if(this.posY > canvas.height){
this.posY = 0-this.image.height;
}
}else if(my < 0) { // if moving up
if(this.posY + this.image.height < 0){
this.posY = canvas.height;
}
}
}
}
function mainLoop() {
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
if(myImage.image.complete) { // wait for image to load
myImage.update();
myImage.draw(ctx);
}else{ // some feedback to say the image is loading
ctx.fillText("Loading image..",canvas.width / 2, canvas.height / 2);
}
// request the next frame
requestAnimationFrame(mainLoop);
}
// request the first frame
requestAnimationFrame(mainLoop);
canvas {
border: 2px solid black;
}
<!-- id's must be unique to the page -->
<canvas id="canvas"></canvas>
Please note that the above code uses ES6 and will need a code pre processor like Babel to run on legacy browsers.
Check out this answer: https://stackoverflow.com/a/30739547/3200577
Basically, the way that html canvas works is very different from how html elements are rendered and painted. Whereas you can select and move an html element on the page, you cannot select and move something that you have added to a canvas because all you can do on a canvas is add and clear pixels. So, when you add an image to a canvas, you are adding the pixels of the image to the canvas. If you were to add the image again but a little to the left, then it would look like you've added two images, where the second one overlaps the first, which is not what you want.
So, to animate the motion of an image on the canvas, you need to:
choose an x and y as the position of the image on the canvas
draw the image on the canvas at x and y
increment the values of x and y to the new position that you want
clear the canvas
redraw the image at the new x and y
A more abstract description of this flow: basically, you need to create, store, and manage your own model of what your canvas looks like; when you want to add, remove of change things that you've painted on the canvas, you actually aren't going to be adding, removing, or changing anything directly on the canvas. You would add, remove and change things in your own model, clear the canvas, and then redraw the canvas on the basis of your model.
For instance, your model might be a JS object such as
myModel = {
images: [
{ url: "my/picture.png", position: [123,556] },
{ url: "another/picture.jpg", position: [95,111] }
]
}
and you would write functions for 1) incrementing the values of the positions of the images in the model, 2) clearing the canvas, and 3) drawing your model onto the canvas. Then, you would create a loop (using requestAnimationFrame or setInterval) that would repeatedly execute those three functions.
For large or complex projects, I strongly recommend using a canvas library such as paperjs, which implements that flow for you (so that you don't have to think about creating a model and clearing and redrawing the canvas). It provides high-level functionality such as animation right out of the box.
I am looking for a way to wrap a bitmap image around the canvas, for an infinite scrolling effect. I'm looking at EaselJS but clean javascript code will also suffice.
Right now I am displacing an image to the left, and when it reaches a certain mark, it resets itself.
Coming from actionscript, there was an option to "wrap" the pixels of a bitmap around to the other side, thereby never really displacing the image, instead you were wrapping the pixels inside the image. Is this possible in javascript with canvas?
My current code:
this.update = function() {
// super large graphic
_roadContainer.x -= 9;
if(_roadContainer.x < -291) _roadContainer.x = 0;
}
Start with a good landscape image.
Flip the image horizontally using context.scale(-1,1).
Combine the flipped image to the right side of the original image.
Because we have exactly mirrored the images, the far left and right sides of the combined image are exactly the same.
Therefore, as we pan across the combined image and “run out of image”, we can just add another copy of the combined image to the right side and we have infinite + seamless panning.
Here's code and a Fiddle: http://jsfiddle.net/m1erickson/ywDp5/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
// thanks Paul Irish for this RAF fallback shim
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var infiniteImage;
var infiniteImageWidth;
var img=document.createElement("img");
img.onload=function(){
// use a tempCanvas to create a horizontal mirror image
// This makes the panning appear seamless when
// transitioning to a new image on the right
var tempCanvas=document.createElement("canvas");
var tempCtx=tempCanvas.getContext("2d");
tempCanvas.width=img.width*2;
tempCanvas.height=img.height;
tempCtx.drawImage(img,0,0);
tempCtx.save();
tempCtx.translate(tempCanvas.width,0);
tempCtx.scale(-1,1);
tempCtx.drawImage(img,0,0);
tempCtx.restore();
infiniteImageWidth=img.width*2;
infiniteImage=document.createElement("img");
infiniteImage.onload=function(){
pan();
}
infiniteImage.src=tempCanvas.toDataURL();
}
img.crossOrigin="anonymous";
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/mountain.jpg";
var fps = 60;
var offsetLeft=0;
function pan() {
// increase the left offset
offsetLeft+=1;
if(offsetLeft>infiniteImageWidth){ offsetLeft=0; }
ctx.drawImage(infiniteImage,-offsetLeft,0);
ctx.drawImage(infiniteImage,infiniteImage.width-offsetLeft,0);
setTimeout(function() {
requestAnimFrame(pan);
}, 1000 / fps);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=500 height=143></canvas><br>
</body>
</html>
You can achieve this quite easily with the html5 canvas.
Look at the drawImage specification here :
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#drawing-images
drawImage comes in 3 flavors, the first being a simple copy of the image, the second allowing to scale the image, and the third is the one you seek, it allows to perform clipping in a single call.
What you have to do :
- have a counter that will move from zero to the width of your image, then loop to zero again.
- each frame, draw the maximum of the image that you can on the canvas.
- If there is still some part of the canvas not drawn, draw again the image starting from zero to fill the canvas.
i made a fiddle, the only part that matters is in the animate function (other things are tools i often use in my fiddles).
(Rq : I assumed in this example that two images would be enough to fill the canvas.)
http://jsfiddle.net/gamealchemist/5VJhp/
var startx = Math.round(startPos);
var clippedWidth = Math.min(landscape.width - startx, canvasWidth);
// fill left part of canvas with (clipped) image.
ctx.drawImage(landscape, startx, 0, clippedWidth, landscape.height,
0, 0, clippedWidth, landscape.height);
if (clippedWidth < canvasWidth) {
// if we do not fill the canvas
var remaining = canvasWidth - clippedWidth;
ctx.drawImage(landscape, 0, 0, remaining, landscape.height,
clippedWidth, 0, remaining, landscape.height);
}
// have the start position move and loop
startPos += dt * rotSpeed;
startPos %= landscape.width;
To answer my own question: I found a way to achieve this effect with EaselJS. The great benefit of this method is that you don't have to check for the position of your bitmap. It will scroll infinitely - without ever resetting the position to 0.
The trick is to fill a shape with a bitmapfill. You can set a bitmapfill to repeat infinitely. Then you use a Matrix2D to decide the offset of the bitmapfill. Since it repeats automatically, this will generate a scrolling effect.
function.createRoad() {
// road has a matrix, shape and image
_m = new createjs.Matrix2D();
// this gets the image from the preloader - but this can be any image
_r = queue.getResult("road");
// this creates a shape that will hold the repeating bitmap
_roadshape = new createjs.Shape();
// put the shape on the canvas
addChild(_roadshape);
}
//
// the draw code gets repeatedly called, for example by requestanimationframe
//
function.drawRoad() {
// var _speed = 4;
_m.translate(-_speed, 0);
_roadshape.graphics.clear().beginBitmapFill(_r, "repeat", _m).rect(0, 0, 900, 400);
}
I'm trying to create some little webcam tool that let's you take snapshots with an overlay. Now I've been working with the html5 canvas and got the webcam working. But whenever I try to save the webcam snapshot it just only loads either the webcam snapshot or the overlay and never both.
I've been trying out a couple things this is the closest I could get:
var teaser = new Image();
teaser.src = "http://www.fillmurray.com/g/1200/1200";
teaser.onload = function() {
context.drawImage(teaser, 0, 0);
};
context.drawImage(v, teaser, 0 , 0, w, h); // this only draws the image
// and
context.drawImage(v, 0 , 0, w, h); // this just draws the webcam snapshot
Does anyone know if this is even possible or if I'm just being a complete idiot? So what I want to achieve is to draw two images in 1 canvas and to let the overlay to be on top of the video.
Here is a jsfiddle of what I have so far: http://jsfiddle.net/Px8g3/
You should do this :
context.globalCompositionOperation = "source-atop"
before drawing (or after the 1st draw, try both)
To be more precise :
var teaser = new Image();
teaser.src = "http://www.fillmurray.com/g/1200/1200";
teaser.onload = function() {
context.drawImage(teaser, 0, 0);
};
//context.save(); Try with and without
context.globalCompositionOperation = "source-atop";
context.drawImage(v, 0 , 0, w, h); // Don't know what v is, but i guess it's an image
//context.restore(); Same
To be precise, this make the canvas to be in "source-atop" mode.
What does it means ?
It means that where the top layer (the 2nd image you draw) is transparent, it will keep the background (video) and draw it. Elsewhere, it will draw your 2nd layer
I have a pixel-array and I would like to draw it onto the canvas, without using the putImageData, because that function ignores clip data.
So, just to make this work i've converted the pixel-array to a data-url, and then appended the url to an image which I then draw onto the canvas, like this:
var ctx = {
display: document.querySelector('canvas#display').getContext('2d'),
convert: document.querySelector('canvas#convert').getContext('2d')
}
var image_data = THE_IMAGE_DATA_THAT_I_ALREADY_HAVE;
ctx.convert.putImageData(image_data, 0, 0);
var image_data_URL = ctx.convert.canvas.toDataURL('image/png');
var converter_image = document.querySelector('img#converter-image');
converter_image.onload = function () {
ctx.display.save();
ctx.display.beginPath();
ctx.display.arc(320, 240, 240, 0, Math.PI*2, true);
ctx.display.clip();
ctx.display.drawImage(converter_image, 0, 0, 640, 480);
ctx.display.restore();
}
converter_image.src = image_data_URL;
However, this is gives a REALLY bad performance, especially since I want to do this 60/sec.
There must be another way, right?
A work around will be to draw your pixel using getImageData / putImageData in an offscreen canvas then drawing the offscreen canvas back in your canvas.
More infos :
Canvas offscreen
Pixel drawing with putImagedata (click on the blank in top of the code to launch it)