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)
Related
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.
I'm creating an application to calculate how much solar panels would fit on a specific roof.
Users can input the dimensions of their roof.
We only have on size of solar panels available.
I thought a canvas was the way to go but I don't seem to find the information I need..
Requirements
1) Based on the input of the user the canvas should be resized (currently I have a rectangle inside the canvas changing to this size)
2) User should be able to create (and size) objects to put on the roof (chimney, window,..)
3) Based on the open space left solar panels (rectangles) should be automaticly drawn on the canvas
Dimensions and limitations
1px = 2cm
Spacing to edge of roof and object is 7px (14cm)
Solar panel is 169 cm height and 102 cm width
I've checked out the fabric.js library but can't seem to find something close to what I need.
The js I got so far to draw the canvas:
var canvas=document.getElementById("c");
var ctx=canvas.getContext("2d");
var width=50;
var height=35;
var $width=document.getElementById('width');
var $height=document.getElementById('height');
var paneelWidth=101;
var peneelHeight=170;
$width.value=width;
$height.value=height;
draw();
$width.addEventListener("keyup", function(){
width=this.value/2;
draw();
}, false);
$height.addEventListener("keyup", function(){
height=this.value/2;
draw();
}, false);
function draw(){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillRect(10,10,width,height)
}
Update
The canvas now does resize in a dynamic way based on user input.
I also found the function createPattern(), which is bringing me closer to the solution.
I've added this code to generate a pattern of solar panels in the canvas:
function placepanels(direction) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
var img = document.getElementById("paneel");
var pat = ctx.createPattern(img, direction);
var w2 = canvas.width - 7;
var h2 = canvas.height - 7;
ctx.rect(7, 7, w2, h2);
ctx.fillStyle = pat;
ctx.fill();
}
The -7 on width and height is beacause I need 14cm space on each size of the canvas. Hence why I offset the rectangle containing the pattern 7px from left and top. Currently not able to achieve this on right and bottom side.
Current issue
The result I'm getting is not looking correct, it seems like the pattern repeats wrong (to much repeats) or it's not getting the proper size of the image to repeat.
Updated fiddle: https://jsfiddle.net/8e05ghqy/3/
As for the canvas resize, this function would do it:
changeCanvasSize = function( width, height ) {
$('canvas').width(width)
$('canvas').height(height)
}
Example of usage: changeCanvasSize(450,250) would change the canvas size to 450px of width and 250px of height.
I am just resizing the HTML <canvas> element .width( value ) and .height( value ) works for any HTML element.
I am trying to get a handle on rotating in canvas and I have not found an answer to my issue.
One square is to remain still. Another square to rotate at a specified point. I can either get the entire canvas to rotate at that point, or neither at all. But I cannot get one square to be static and one to rotate.
Please view my codepen demo here: http://codepen.io/richardk70/pen/EZWMXx
The javascript:
HEIGHT = 400;
WIDTH = 400;
function draw(){
var ctx = document.getElementById("canvas").getContext('2d');
ctx.clearRect(0,0,WIDTH,HEIGHT);
// draw black square
ctx.save();
ctx.fillStyle = "black";
ctx.beginPath();
ctx.fillRect(75,75,100,100);
ctx.closePath();
ctx.restore();
// attempt to rotate small, maroon square only
ctx.save();
ctx.translate(225,225);
// small maroon square
ctx.fillStyle = "maroon";
ctx.beginPath();
ctx.fillRect(-25,-25,50,50);
ctx.closePath();
ctx.rotate(.1);
ctx.translate(-225,-225);
// comment out below line to spin
// ctx.restore();
window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);
I know I could do layered canvases, but surely it can be done in just one canvas layer? Isn't the clock in this tutorial (https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations) doing exactly that?
Thanks for any and all help.
To rotate only one image or rendering you need to restore the canvas transform state back to the default.
This function will render your object rotated scaled and translated but not affect any other rendering.
BTW you don't need to use beginPath is the render calls start with fill or stroke such as ctx.fillRect, ctx.strokeRect, ctx.fillText, ctx.strokeText and you only need to use ctx.closePath if you need to create a line from the last path point to the previous ctx.moveTo point or first pathpoint after a ctx.beginPath call
Nor do you need to use save and restore for all rendering. You only use it if you need to get the previous canvas state back.
ctx = canvas.getContext("2d");
function drawBox(col,size,x,y,scale,rot){
ctx.fillStyle = col;
// use setTransform as it overwrites the canvas transform rather than multiply it as the other transform functions do
ctx.setTransform(scale, 0, 0, scale, x, y);
ctx.rotate(rot);
ctx.fillRect(-size / 2,-size / 2, size, size);
}
function update(time){
ctx.clearRect(0,0,512,256)
drawBox("black",100,125,125,1,0); // draw none rotated box
drawBox("maroon",50,225,125,1,time / 500); // draw rotating box
drawBox("green",25,275,100,1,-time / 250); // draw rotating box
drawBox("green",25,275,150,1,-time / 250); // draw rotating box
// after using transforms you need to reset the transform to the default
// if you plan to render anything in the default coordinate system
ctx.setTransform(1, 0 ,0 , 1, 0, 0); // reset to default transform
requestAnimationFrame(update);
}
requestAnimationFrame(update);
<canvas id="canvas" width="512" height="256"></canvas>
One way I can think of is with an animated svg, but there is probably a better way. What would you do if you had to animate these wavy blobs (mobile compatible)
Link to the only pin I've found similar
var wave = document.createElement("div");
wave.className += " wave";
docFrag.appendChild(wave);
wave.style.left = i * waveWidth + "px";
wave.style.webkitAnimationDelay = (i / 100) + "s";
Touch interaction would be nice too. Would there be any problems with canvas stuff ?
Here's an implementation of #DA.'s good answer:
var canvas=document.getElementById('canvas');
var ctx=canvas.getContext('2d');
var cw=canvas.width;
var ch=canvas.height;
ctx.textAlign='center';
ctx.textBaseline='middle';
ctx.font='16px verdana';
ctx.lineWidth=5;
ctx.strokeStyle='white';
ctx.fillStyle='white';
var offsetX=0;
var bk=makeWave(canvas.width,canvas.height-120,10,2,'lightskyblue','cornflowerblue');
requestAnimationFrame(animate);
function animate(time){
ctx.clearRect(0,0,cw,ch);
ctx.drawImage(bk,offsetX,0);
ctx.fillStyle='white';
ctx.font='18px verdana';
ctx.fillText('Multiple Lists',cw/2,30);
ctx.strokeRect(cw/2-50,85,100,50);
ctx.fillStyle='gray';
ctx.font='12px verdana';
ctx.fillText('You can create and save multiple ...',cw/2,250);
offsetX-=1;
if(offsetX< -bk.width/2){offsetX=0;}
requestAnimationFrame(animate);
}
function makeWave(width,midWaveY,amplitude,wavesPerWidth,grad0,grad1){
var PI2=Math.PI*2;
var totValue=PI2*wavesPerWidth;
var c=document.createElement('canvas');
var ctx=c.getContext('2d');
c.width=width*2;
c.height=midWaveY+amplitude;
var grad=ctx.createLinearGradient(0,0,0,midWaveY);
grad.addColorStop(0.00,grad0);
grad.addColorStop(1.00,grad1);
//
ctx.beginPath();
ctx.moveTo(0,0);
for (x=0;x<=200;x++) {
var n=totValue*x/100;
ctx.lineTo(width*x/100,Math.sin(n)*amplitude+midWaveY);
}
ctx.lineTo(c.width,0);
ctx.closePath();
ctx.fillStyle=grad;
ctx.fill();
return(c);
}
body{ background-color:white; }
canvas{border:1px solid red; margin:0 auto; }
<canvas id=canvas width=300 height=300></canvas>
I'd make the wave a PNG (bottom solid gray, top transparent). Place it in a div twice the width of the card, and place that in a div the width of the card (this second div is the 'mask').
Then via CSS, have the nested give transform on the x axis to animate it sideways.
You shouldn't need any JS for this.
I would do this:
on page load create an off-screen canvas (just set display: none)
in a for loop compute the wave:
clear with transparency
paint only the white part because the colored part has a gradient
after each paint, get the PNG data out of the canvas and store it in an array
after the loop you will have an array of PNG images (frames)
cycle through those frames without recomputing the wave over and over again
This requires the wave to have a period that is affine to the number of frames you take (say a 2 second animation at 10 Hz would require 20 frames to be cyclic)
To be honest, you could store that server-side and just download it, without computing it client-side. Those PNG images would be very tiny because there isn't any color involved (just transparent/white/alpha channel). There are optimal settings for this, I guesstimate some 1KB per frame would suffice, that's a tiny 20 KB of images).
I want to set a global clipTo in my Fabric-powered Canvas that will affect all user-added layers. I want a background image and an overlay image, which are unaffected by this clip mask.
Example:
Here's what's happening in this photo:
A canvas overlay image makes the t-shirt look naturally wrinkled. This overlay image is mostly transparent
A background image in the exact shape of the t-shirt was added, which is supposed to make the t-shirt look blue
A canvas.clipTo function was added, which clips the canvas to a rectangular shape
A user-added image (the famous Fabric pug) was added
I want the user-added image (the pug) to be limited to the rectangular area.
I do not want the background image (the blue t-shirt shape) affected by the clip area.
Is there a simple way to accomplish this? I really don't want to have to add a clipTo on every single user layer rather than one tidy global clipTo.
You can play with a JS fiddle showing the problem here.
I came here with the same need and ultimately found a solution for what I'm working on. Maybe it helps:
For SVG paths, within the clipTo function you can modify the ctx directly prior to calling render(ctx) and these changes apply outside the clipped path o. Like so:
var clipPath = new fabric.Path("M 10 10 L 100 10 L 100 100 L 10 100", {
fill: 'rgba(0,0,0,0)',
});
var backgroundColor = "rgba(0,0,0, 0.2)";
var opts = {
controlsAboveOverlay: true,
backgroundColor: 'rgb(255,255,255)',
clipTo: function (ctx) {
if (typeof backgroundColor !== 'undefined') {
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, 300, 150);
}
clipPath.render(ctx);
}
}
var canvas = new fabric.Canvas('c', opts);
canvas.add(new fabric.Rect({
width: 50,
height: 50,
left: 30,
top: 30,
fill: 'rgb(255,0,0)'
}));
You can of course add an image instead of a color, or whatever else you want done. The trick I've found is to put it in the clipTo function on the ctx directly.
here's a fiddle
One (sorta hacky) solution: set a CSS background image on your canvas element, as shown in https://jsfiddle.net/qpnvo3cL/
<canvas id="c" width="500" height="500"></canvas>
<style>
background: url('http://fabricjs.com/assets/jail_cell_bars.png') no-repeat;
</style>
<script>
var canvas = window._canvas = new fabric.Canvas('c');
canvas.clipTo = function(ctx) {
ctx.rect(100,100,100,100);
}
</script>
Have you tried clipping a fabric Group? You could make the whole shirt one canvas. The center graphics would be one Group which you clip to where you want it. The white t-shirt and the blue overlay would of course not be part of the clipped group.
Here's an example of clipping a group:
var rect = new fabric.Rect({width:100, height: 100, fill: 'red' });
var circle = new fabric.Circle({ radius: 100, fill: 'green' });
var group1 = new fabric.Group([ circle, rect ], { left: 100, top: 100 });
canvas.add(group1);
group1.clipTo = function(ctx) {
ctx.rect(50,50,200,200);
};
See this jsfiddle I made: https://jsfiddle.net/uvepfag5/4/
I find clip rather slow so I tend to use globalCompositeOperation to do masking.
If you really need to use clip then use it in conjunction with save and restore.
// ctx is canvas context 2d
// pug is the image to be clipped
// draw your background
ctx.save(); // save state
ctx.rect(100,100,100,100); // set the clip area
ctx.clip(); // apply the clip
ctx.drawImage(pug,x,y); // draw the clipped image
ctx.restore(); // remove the clipping
// draw the other layers.
or you can
// draw background
ctx.globalCompositeOperation = "xor"; // set up the mask
ctx.fillRect(100,100,100,100); // draw the mask, could be an image.
// Alpha will effect the amount of masking,
// not available with clip
ctx.globalCompositeOperation = "destination-over";
ctx.drawImage(pug,x,y); // draw the image that is masked
ctx.globalCompositeOperation = "source-over";
// draw the stuff that needs to be over everything.
The advantage of composite operations is you have control over the clipping at a per pixel level, including the amount of clipping via the pixel alpha value