Rotating and drawing on a p5.js canvas - javascript

I am trying to draw on a rotating p5.js canvas, where the canvas element is being rotated via its transform CSS attribute. The user should be able to draw points on the canvas with the cursor, even while the canvas element is actively rotating.
The below code is what I have tried so far. However, it is currently not working, because the points are not correctly showing up where the mouse is hovering over the rotating canvas. I'm currently testing this out on the p5.js editor.
let canvas
function setup() {
canvas = createCanvas(400, 400)
background('#fb88f3')
strokeWeight(10)
}
function draw() {
canvas.style(`transform: rotate(${frameCount}deg)`)
translate(200, 200)
rotate(-radians(frameCount))
point(mouseX-200, mouseY-200)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
I've found this related StackOverflow post where they drew on a rotating canvas as a p5.Graphics element. However, for my purposes, I'd like to rotate the actual element instead, as part of a simple p5.js painting application I am working on.
I've been stuck on this for a while and would appreciate any help! Thanks in advance!

This seems to be a bug in p5.js.
The mouse coordinate seems to depend on the size of the bounding box of the canvas. The bounding box depends on the rotation. Therefor you need to scale the offset for the calculation of the center of the canvas.
Unfortunately, that's not all. All of this does not depend on the current angle, but on the angle that was set when the mouse was last moved. Hence the scale needs to be computed in the mouseMoved callback:
let scale;
function mouseMoved() {
let angle_rad = radians(frameCount)
scale = abs(sin(angle_rad)) + abs(cos(angle_rad))
}
let center_offset = 200 * scale;
point(mouseX-center_offset, mouseY-center_offset)
let canvas, scale
function setup() {
canvas = createCanvas(400, 400)
background('#fb88f3')
strokeWeight(10)
}
function draw() {
let angle = frameCount;
let angle_rad = radians(angle)
let center_offset = 200 * scale
canvas.style(`transform: rotate(${angle}deg)`)
translate(200, 200)
rotate(-angle_rad)
point(mouseX-center_offset, mouseY-center_offset)
}
function mouseMoved() {
let angle_rad = radians(frameCount)
scale = abs(sin(angle_rad)) + abs(cos(angle_rad))
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>

Related

javascript loaded image is blurry (PNG)

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)

Fill area with fixed size elements

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.

How to make the effect of moving the image in the window canvas?

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.

How to draw outside of an HTML canvas element?

I have an HTML canvas element and have implemented a brush that captures the mousedown, mousemove, and mouseup events of the canvas element. This all works fine for drawing on the canvas. However, I don't think I like how you can't continue a drawing if your mouse leaves the canvas mid stroke. It kind of just cuts it off. It's very unforgiving to the person and not very user-friendly in my opinion.
If you open up Microsoft Paint and begin drawing with the brush or ellipse or something, as long as you start within the canvas, you can drag your mouse anywhere on your screen and re-enter the canvas wherever. It also makes it easy, for example, to draw quarter-circles in corners because you can drag the ellipse tool off-screen. I hope this makes sense.
Anyways, I was wondering if there was a way to implement this with the HTML5 canvas or how I would go about implementing this sort of thing. The user would never have to actually seen anything drawn over there; it's mostly just going to be a feature for usability.
Edit: A problem with a lot of these solutions is how to handle coordinates. Currently my canvas is in the middle of the screen and the top left of the canvas is (0, 0) and the bottom right is (500, 500). The translation work of the coordinates has to be considered as well.
Edit2: I found out that apparently you can draw off the canvas bounds just fine. For example, you can supply negative widths, heights, and coordinates and the canvas element will handle it just fine. So basically the solution will likely involve just capturing the document's mousemove and mouseup and just translating the x and y to start at the canvas's top left corner.
Here is one way you can keep drawing when reenter the canvas:
Create a global variable and set that one to true on mousedown
Add a global event for mouseup so you can catch if someone do that outside
the canvas, and if so, set global variable to false, and the canvas element's mouseup need of course also to set the same variable
On mousemove, check for global variable to be true before draw
To draw "outside" the canvas, like quarter-circles in a corner, I would move all events to the document level as global handler and catch the canvas element on click and pass its client coordinates to be computed with the document coordinates.
Here is an extremely rough first draft of how you can listen for mouse events on the window rather than the canvas to be able to draw continuously:
var logger = document.getElementById("logger"),
mState = document.getElementById("mState"),
mX = document.getElementById("mX"),
mY = document.getElementById("mY"),
cX = document.getElementById("cX"),
cY = document.getElementById("cY"),
c = document.getElementById("canvas"),
ctx = c.getContext("2d");
var mouse = {
x: 0,
y: 0,
state: ""
};
function printCanvasLocation() {
var b = c.getBoundingClientRect();
cX.innerHTML = b.top;
cY.innerHTML = b.left;
}
function setState(mouseE, state) {
mouse.x = mouseE.clientX;
mouse.y = mouseE.clientY;
mX.innerHTML = mouseE.clientX;
mY.innerHTML = mouseE.clientY;
if (state) {
mState.innerHTML = state;
mouse.state = state;
}
}
window.addEventListener("mousedown", function(mouseE) {
setState(mouseE, "down");
});
window.addEventListener("mouseup", function(mouseE) {
setState(mouseE, "up");
});
window.addEventListener("mousemove", function(mouseE) {
var offset = c.getBoundingClientRect();
var fix = {
x1: (mouse.x - offset.left),
y1: (mouse.y - offset.top),
x2: (mouseE.clientX - offset.left),
y2: (mouseE.clientY - offset.top)
};
if (mouse.state === "down") {
ctx.moveTo(fix.x1, fix.y1);
ctx.lineTo(fix.x2, fix.y2);
ctx.strokeStyle = "#000";
ctx.stroke();
}
setState(mouseE);
});
window.addEventListener("resize", function() {
printCanvasLocation();
});
printCanvasLocation();
.center {
text-align: center;
}
canvas {
background-color: lightblue;
}
<main>
<div class="center">
<canvas id="canvas" width="128" height="128">If you can see me, you should update your browser</canvas>
</div>
<div id="logger" role="log">
<span>State: </span><span id="mState">Unknown</span>
<span>X: </span><span id="mX">Unknown</span>
<span>Y: </span><span id="mY">Unknown</span>
<span>Canvas X: </span><span id="cX">Unknown</span>
<span>Canvas Y: </span><span id="cY">Unknown</span>
</div>
</main>
One solution would be to literally make the canvas the size of the window, and scale its size with it. The canvas can be mostly transparent.
I'm sure there's also a way to make mouse events and such go through the canvas first but then pass through to the elements behind, if that's desired. (See: "js events bubbling and capturing".)
But then you would have absolute control and be able to draw anything anywhere.

Flip a sprite in canvas

I'm using canvas to display some sprites, and I need to flip one horizontally (so it faces left or right). I can't see any method to do that with drawImage, however.
Here's my relevant code:
this.idleSprite = new Image();
this.idleSprite.src = "/game/images/idleSprite.png";
this.idleSprite.frameWidth = 28;
this.idleSprite.frameHeight = 40;
this.idleSprite.frames = 12;
this.idleSprite.frameCount = 0;
this.draw = function() {
if(this.state == "idle") {
c.drawImage(this.idleSprite, this.idleSprite.frameWidth * this.idleSprite.frameCount, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight, this.xpos, this.ypos, this.idleSprite.frameWidth, this.idleSprite.frameHeight);
if(this.idleSprite.frameCount < this.idleSprite.frames - 1) { this.idleSprite.frameCount++; } else { this.idleSprite.frameCount = 0; }
} else if(this.state == "running") {
c.drawImage(this.runningSprite, this.runningSprite.frameWidth * this.runningSprite.frameCount, 0, this.runningSprite.frameWidth, this.runningSprite.frameHeight, this.xpos, this.ypos, this.runningSprite.frameWidth, this.runningSprite.frameHeight);
if(this.runningSprite.frameCount < this.runningSprite.frames - 1) { this.runningSprite.frameCount++; } else { this.runningSprite.frameCount = 0; }
}
}
As you can see, I'm using the drawImage method to draw my sprites to the canvas. The only way to flip a sprite that I can see is to flip/rotate the entire canvas, which isn't what I want to do.
Is there a way to do that? Or will I need to make a new sprite facing the other way and use that?
While Gaurav has shown the technical way to flip a sprite in canvas...
Do not do this for your game.
Instead make a second image (or make your current image larger) that is a flipped version of the spite-sheet. The performance difference between "flipping the context and drawing an image" vs "just drawing an image" can be massive. In Opera and Safari flipping the canvas results in drawing that is ten times slower, and in Chrome it is twice as slow. See this jsPerf for an example.
Pre-computing your flipped sprites will always be faster, and in games canvas performance really matters.
You can transform the canvas drawing context without flipping the entire canvas.
c.save();
c.scale(-1, 1);
will mirror the context. Draw your image, then
c.restore();
and you can draw normally again. For more information, see https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Transformations
Use this to flip your sprite sheet
http://flipapicture.com/

Categories