This question already has answers here:
Canvas is stretched when using CSS but normal with "width" / "height" properties
(10 answers)
Closed 1 year ago.
For some reason, if you set the canvas aspect ratio to 2:1, everything appears normal; any other aspect ratio will make it stretch.
const canvas = document.getElementById('canvas');
canvas.style.width = '400px';
canvas.style.height = '400px';
canvas.style.border = '2px solid black';
const ctx = canvas.getContext('2d');
const w = canvas.width/4;
const h = canvas.height/4;
var relativeAngle = 0;
//(Target is the position I want it; in this case, right in the middle)
const targetX = canvas.width/2;
const targetY = canvas.height/2;
const targetDist = Math.sqrt(targetX ** 2 + targetY ** 2);
const targetAngle = Math.atan(targetY / targetX) * 180 / Math.PI;
ctx.fillStyle = 'red'
function draw(){
relativeAngle += 3;
let targetRelativeAngle = targetAngle - relativeAngle;
let targetRelativeX = targetDist * Math.cos(targetRelativeAngle * Math.PI / 180);
let targetRelativeY = targetDist * Math.sin(targetRelativeAngle * Math.PI / 180);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
ctx.rotate(relativeAngle * Math.PI / 180);
ctx.translate(targetRelativeX,targetRelativeY);
ctx.fillRect(-w/2,-h/2,w,h);
ctx.restore();
window.requestAnimationFrame(draw);
}
draw();
What is the problem here and/or what is the better way to do this?
https://jsfiddle.net/1gx7bv3m/
My experience not a good idea to mix styles with canvas...
We can set the aspect ratio directly on the canvas like this:
const canvas = document.getElementById('canvas');
canvas.width = canvas.height = 400
const ctx = canvas.getContext('2d');
const w = canvas.width / 4;
const h = canvas.height / 4;
ctx.fillStyle = 'red'
function refresh() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(w, h)
ctx.rotate(0.02);
ctx.fillRect(-w / 2, -h / 2, w, h);
ctx.translate(-w, -h)
window.requestAnimationFrame(refresh);
}
refresh();
<canvas id="canvas"></canvas>
You can see that I also remove a lot of the calculations you had and I hope that I accomplish the same result you are looking for, a lot less code and much more readable.
Related
I want to clear the canvas with condition on each iteration, may be performance. And, the condition is to only want to clear the line and not the arc.
I already tried chatGPT, save and restore method in JS for save the previous canvas, but it didn't work for me.
This line of code uses the clearRect method of the 2D rendering context to clear the canvas by specifying the x, y, width, and height. But, its clear the whole canvas which I don't want as I mention earlier.
For the better context of my question, you can see this image.
Any answer will be appreciated.
const canvas = document.getElementById('cvs')
const ctx = canvas.getContext('2d')
const CH = (canvas.height = 400)
const CW = (canvas.width = 400)
canvas.style.background = 'black'
const vs = () => {
let radius = 50;
let i = 0;
setInterval(() => {
let x = Math.cos(i) * radius + CH / 2;
let y = Math.sin(i) * radius + CW / 2;
if(i>2*Math.PI)return clearInterval();
/* if I add `ctx.clearRect(0, 0, CW, CH);` it's clear the whole canvas, I don't want that type of behavior */
ctx.beginPath();
ctx.arc(x,y,1,0,2*Math.PI)
ctx.closePath();
ctx.strokeStyle = "white";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(x, y);
ctx.closePath();
ctx.strokeStyle = "red";
ctx.stroke();
i += 0.01;
}, 10)
}
vs();
body{
display:grid;
place-items:center;
min-height:100vh;
background:gray;
}
<canvas id="cvs"></canvas>
I already tried chatGPT, save and restore method in JS for save the previous canvas, but it didn't work for me.
Those store-restore the context state, color in use and the like. If you look into the box in the top-right, coincidentally you will see a link "Temporary policy: ChatGPT is banned" - that has a reason.
What you need is storing-restoring the bitmap data, getImageData() and putImageData() are the methods for that. If you're concerned about performance (though it feels a bit early), the arc() call can be skipped too, as with this step-size you won't end up with a dotted circle (of course the strokeRect() I'm using instead could be then replaced with direct pixel manipulation, but a cool thing is that it provides anti-aliasing):
const canvas = document.getElementById('cvs')
const ctx = canvas.getContext('2d')
const CH = (canvas.height = 400)
const CW = (canvas.width = 400)
canvas.style.background = 'black'
const vs = () => {
let radius = 50;
let i = 0;
let backup = false;
setInterval(() => {
let x = Math.cos(i) * radius + CH / 2;
let y = Math.sin(i) * radius + CW / 2;
if (backup) ctx.putImageData(backup, 0, 0);
if (i > 2 * Math.PI) return clearInterval();
ctx.strokeStyle = "white";
ctx.strokeRect(x, y, 2, 2);
/* ctx.beginPath();
ctx.arc(x,y,1,0,2*Math.PI)
ctx.closePath();
ctx.strokeStyle = "white";
ctx.stroke();*/
backup = ctx.getImageData(0, 0, x + 2, y + 2); // 0 or 1 will have some red pixels
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(x, y);
ctx.closePath();
ctx.strokeStyle = "red";
ctx.stroke();
i += 0.01;
}, 10)
}
vs();
body {
display: grid;
place-items: center;
min-height: 100vh;
background: gray;
}
<canvas id="cvs"></canvas>
I have a very specific project I'm working on and after 2 weeks the best option seems to be using a bitmap within an empty movie clip. It's perfect apart from one issue - I can't figure out how to pixelate the image.
Here is my code so far:
init_image = () => {
props.image = null
_.holder_grid.bitmap = null
props.image = new Image()
props.image.src = "images/myImage.jpg"
props.image.onload = function() {
stage.holder_grid.bitmap = new createjs.Bitmap(props.image)
stage.holder_grid.bitmap.scale = props.image_scale
stage.holder_grid.addChild(_.holder_grid.bitmap);
const coords = redraw.get_centre()
stage.holder_grid.bitmap.x = coords.x
stage.holder_grid.bitmap.y = coords.y
settings.update()
}
}
init_image()
Its all working as I want but I can't figure out how to pixelate the image.
I found one solution where I draw the image using canvas.context.drawImage() but due to the parameters of the project it's not adequate. That worked like so:
const canvas = document.getElementById("canvas2")
const base = document.getElementById("canvas")
const ctx = canvas.getContext("2d")
const image = new Image()
props.image = image
image.src = "images/myImage.jpg"
image.onload = function() {
const width = base.clientWidth
const height = base.clientHeight
canvas.width= width
canvas.height= height
const scale= props.image_scale
const x = (ctx.canvas.width - image.width * scale) / 2
const y = (ctx.canvas.height - image.height * scale) / 2
//draws tiny
const size = props.pixelate/1
const w = canvas.width * size
const h = canvas.height * size
ctx.drawImage(image, 0, 0, w, h);
// turn off image aliasing
ctx.msImageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
// enlarge the minimized image to full size
ctx.drawImage(canvas, 0, 0, w, h, x, y, image.width * scale, image.height * scale);
}
So basically you draw it small then use that small instance as the image source to draw it bigger and viola, it's pixelated.... but due to other issues I've encountered doing this I cannot use this method.
Can anyone help me with this issue please?
The pixelation in your example is accomplished by drawing the original image at a lower resolution to an html <canvas> element. With createJS however you don't have built-in support for manipulating the sources of it's own Bitmap object.
There's hope though. Besides URL's to images, the constructor to a Bitmap also accepts a <canvas> element. So the trick here is loading the image, preparing - thus pixelating it - using a temporary <canvas> and once it's done, pass this element to the Bitmap constructor.
For example:
let stage = new createjs.Stage("canvas");
function pixelate(src) {
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
let image = new Image();
image.crossOrigin = "anonymous";
image.onload = (e) => {
canvas.width = e.target.width;
canvas.height = e.target.height;
const width = e.target.width;
const height = e.target.height;
canvas.width = width;
canvas.height = height;
const scale = 1;
const x = (ctx.canvas.width - image.width * scale) / 2;
const y = (ctx.canvas.height - image.height * scale) / 2;
const size = 0.125 / 1;
const w = canvas.width * size;
const h = canvas.height * size;
ctx.drawImage(e.target, 0, 0, w, h);
ctx.msImageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
ctx.drawImage(canvas, 0, 0, w, h, x, y, e.target.width * scale, e.target.height * scale);
let bitmap = new createjs.Bitmap(canvas);
stage.addChild(bitmap);
stage.update();
}
image.src = src;
}
pixelate("https://api.codetabs.com/v1/proxy?quest=https://picsum.photos/id/237/400/300");
<script src="https://code.createjs.com/1.0.0/createjs.min.js"></script>
<canvas id="canvas" width="400" height="300"></canvas>
Link to JSFiddle for entire code: https://jsfiddle.net/u4mk0gdt/
I read the Mozilla docs on save() and restore() and I thought that "save" saved the current state of the entire canvas and "restore" restored the canvas to the most recent "save" state. Hence I placed the saves and restores in such a way that it should clear the white line that is drawn to canvas after is is drawn. However when I run this code the white line is never cleared from the canvas and is drawn continually without clearing.
ctx.restore();
ctx.save(); // <--should save blank canvas
//DRAW LINE
ctx.moveTo(tMatrix.x1, tMatrix.y1);
ctx.lineTo(w/2,h/2);
ctx.strokeStyle = "white";
ctx.stroke();
ctx.restore(); // <-- should restore to the "save()" above
ctx.save(); // <-- <--should save blank canvas again
As you can see, I made a lot of modifications to your code:
console.log("rotating_recs");
// create canvas and add resize
var canvas, ctx;
function createCanvas() {
canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 1000;
document.body.appendChild(canvas);
}
function resizeCanvas() {
if (canvas === undefined) {
createCanvas();
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
}
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
var Player = function(x, y, height, width, rot) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.rot = rot;
this.objWinX = 0; //translate the window object and then apply to this
this.objWinY = 0;
this.draw = function() {
//rotate by user.rot degrees, from the players center
ctx.translate(this.x + this.width / 2, this.y + this.height / 2)
ctx.rotate(this.rot * Math.PI / 180)
ctx.translate(-this.x - this.width / 2, -this.y - this.height / 2)
ctx.fillStyle = "grey";
ctx.fillRect(this.x, this.y, this.height, this.width);
ctx.translate(this.x + this.width / 2, this.y + this.height / 2)
ctx.rotate(-this.rot * Math.PI / 180)
ctx.translate(-this.x - this.width / 2, -this.y - this.height / 2)
}
}
var user = new Player(0, 0, 40, 40, 0);
var user2 = new Player(0, 0, 40, 40, 0);
let rot = 0;
function update(time) {
var w, h;
w = canvas.width; // get canvas size incase there has been a resize
h = canvas.height;
ctx.clearRect(0, 0, w, h); // clear the canvas
//MIDDLE RECT
/*
if you don't want this you can just translate by w/2 and h/2, but I would recommend just making the p layers position the middle
*/
user.x = w / 2 - 20;
user.y = h / 2 - 20;
user.rot += 0.5 // or whatever speed
user.draw(); //draw player -- look at the draw function I added some stuff
//LINE
/*
I don't know what you are trying to do, but I just drew the line to the user2's position,
if this doesn't work for your scenario you can change it back
*/
ctx.beginPath()
ctx.moveTo(user2.x + user2.width/2, user2.y + user2.height/2);
ctx.lineTo(w / 2, h / 2);
ctx.strokeStyle = "white";
ctx.stroke();
//FAST SPIN RECT
/*
There are multiple ways to do this, the one that I think you should do, is actually change the position of user two, this uses some very simple trigonometry, if you know this, this is a great way to do this, if not, you can do it how you did previously, and just translate to the center, rotate, and translate back. Similar to what I did with the player draw function. I am going to demonstrate the trig way here:
*/
user2.rot += 5
rot += 2;
user2.x = w/2 + (w/2) * Math.cos(rot * (Math.PI/180))
user2.y = h/2 + (w/2) * Math.sin(rot * (Math.PI/180))
user2.draw();
//RED RECT
ctx.fillStyle = 'red';
ctx.fillRect(140, 60, 40, 40);
requestAnimationFrame(update); // do it all again
}
requestAnimationFrame(update);
While I think you should add some of these modifications into you code, they are not super necessary. To fix you line problem, all you had to do was add ctx.beginPath() before you drew it. The demonstration that I made was not very good (hence demonstration), and you probably shouldn't use it exactly, but definitely look over it. The modified code for you line drawing would look like:
//LINE
ctx.beginPath()
ctx.moveTo(tMatrix.x1, tMatrix.y1);
ctx.lineTo(w/2,h/2);
ctx.strokeStyle = "white";
ctx.stroke();
ctx.restore();
ctx.save();
Hope this helps :D
Sorry for bad spelling
I have the following in which I get an audioBuffer audio clip and then I draw a circle of sound bars to visualize it:
const { audioContext, analyser } = this.getAudioContext();
const source = audioContext.createBufferSource();
source.buffer = this.props.audioBuffer;
analyser.fftSize = 256;
source.connect(analyser).connect(audioContext.destination);
source.start();
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");
// find the center of the window
let center_x = canvas.width / 2;
let center_y = canvas.height / 2;
let radius = 150;
const frequency_array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(frequency_array);
const bars = analyser.frequencyBinCount;
const bar_width = 2;
animationLooper();
function animationLooper(){
//canvas.height = window.innerHeight;
// find the center of the window
center_x = canvas.width / 2;
center_y = canvas.height / 2;
radius = 50;
analyser.getByteFrequencyData(frequency_array);
for(let i = 0; i < bars; i++){
//divide a circle into equal parts
let rads = Math.PI * 2 / bars;
const bar_height = frequency_array[i] / 2;
// set coordinates
let x = center_x + Math.cos(rads * i) * (radius);
let y = center_y + Math.sin(rads * i) * (radius);
const x_end = center_x + Math.cos(rads * i)*(radius + bar_height);
const y_end = center_y + Math.sin(rads * i)*(radius + bar_height);
//draw a bar
drawBar(x, y, x_end, y_end, bar_width);
}
window.requestAnimationFrame(animationLooper);
}
function drawBar(x1, y1, x2, y2, width){
ctx.strokeStyle = "#1890ff";
ctx.lineWidth = width;
ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.stroke();
}
HTML:
<canvas id="canvas" width="400" height="400" />
This results in this after audio finished playing and the drawing completes. It should return back to no blue lines.
However if I comment in the line
canvas.height = window.innerHeight;
Then it correctly visualizes the audio and at the end the blue lines disappear. But I want a fixed height and width for my canvas. The end result should be lines/sound bars coming out from a center circle.
Does anyone know why the blue lines dont disappear when audio finished playing when I dont have that line commented in?
Yes, clearRect works well if you don't have other elements to draw on the same canvas for each frame. However, it will not work if you only want to remove one of the elements on the canvas.
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
The other issue is that you may need some kinds of transaction effects (such as audio bar fade out) through frames for a certain element.
If you keep drawing the effects for one element, it can clear all other elements during those frames.
The solution I came up with is to keep all elements in an array.
Then draw the array with a boolean tag that decides whether to draw or not for a certain element.
I have a 2D canvas and drawing circle indefinitely one above the other.
Take this example : http://jsfiddle.net/umaar/fnMvf/
<html>
<head>
</head>
<body>
<canvas id="canvas1" width="500" height="500"></canvas>
</body>
</html>
JavaScript :
var currentEndAngle = 0
var currentStartAngle = 0;
var currentColor = 'black';
var lineRadius = 75;
var lineWidth = 15;
setInterval(draw, 50);
function draw() {
var can = document.getElementById('canvas1'); // GET LE CANVAS
var canvas = document.getElementById("canvas1");
var context = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius;
var width;
var startAngle = currentStartAngle * Math.PI;
var endAngle = (currentEndAngle) * Math.PI;
currentStartAngle = currentEndAngle - 0.01;
currentEndAngle = currentEndAngle + 0.01;
if (Math.floor(currentStartAngle / 2) % 2) {
currentColor = "white";
radius = lineRadius - 1;
width = lineWidth + 3;
} else {
currentColor = "black";
radius = lineRadius;
width = lineWidth;
}
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
context.lineWidth = width;
context.lineCap = "round";
// line color
context.strokeStyle = currentColor;
context.stroke();
}
Do I really need to clear canvas at some specific interval ?
How does canvas work in that case ? As it is '2D' context, does it still store previous data ? If yes, What should be approach to achieve smoothness for drawing circle keeping performance in mind ?
Canvas is a drawing surface. When you draw an element (e.g. call fill method), you are just changing the color of some pixels on the drawing surface. The canvas does not store any information about the element being drawn. In your example, there is no need to clear the canvas.