the question may seem simple, but I can not understand what is my mistake.
I draw images on my canvas, everything is fine, but at the moment when I add clearRect, the elements are completely removed from the canvas without restoring them. Although the cleanup cycle should happen before the rendering
what's my mistake? everything seems to be simple, but I'm stuck with it for a very long time
App main game class
Game class - responsible for rendering to the canvas and clearing it
player - responsible for the logic of the player's behavior
animation - requestanimationframe is called in this function, on the function passed to it
class App {
constructor() {
this.player = new Player("Player 1", 0, 102);
this.game = new Game();
this.game.CreateCanvas();
window.addEventListener('keyup', () => this.player.UpKey());
window.addEventListener('keydown', (event)=> this.player.HandlerKeyPress(event));
new Animation(this.Display.bind(this));
}
Display() {
this.game.ClearCanvas();
this.SetLevel();
}
SetLevel() {
this.game.LoadSprite(assets.mario, [...player_assets.mario.small.standing_right, ...this.player.GetPosition(), 16, 16]);
this.StaticObject();
}
StaticObject () {
for(let i = 0; i < 32; i++){
this.game.LoadSprite(assets.block, [...lvl_1.block.ground.size, i* 16, 134, 16,16]);
this.game.LoadSprite(assets.block, [...lvl_1.block.ground.size, i* 16, 118, 16,16]);
}
}
}
new App();
export default class Game {
CreateCanvas () {
this.canvas = document.createElement("canvas");
this.ctx = this.canvas.getContext("2d");
document.body.appendChild(this.canvas);
}
ClearCanvas() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
SetBackgroundColor (color) {
this.ctx.fillStyle = color;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
LoadSprite (src, position) {
const _image = new Image();
_image.src = src;
_image.onload = () => {
this.ctx.drawImage(_image, ...position, true);
}
}
}
export default class LoopAnimation {
constructor(display) {
this.display = display;
this.Animation = this.Animation.bind(this);
this.Animation();
}
Animation() {
requestAnimationFrame(this.Animation);
this.display();
}
}
My suspicion is it's related to cache and event onload not firing. image.onload event and browser cache
suggests to set the onload property before the src.
var img = new Image();
img.onload = function () {
alert("image is loaded");
}
img.src = "img.jpg";
If you want to reuse loaded images, then store them in an array. Then reuse the image from there. See for example:
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.width = 450;
canvas.height = 250;
const ctx = canvas.getContext("2d");
const assets = [
"https://picsum.photos/id/237/150",
"https://picsum.photos/id/235/150",
"https://picsum.photos/id/232/150",
];
const assetsLoaded = assets.map(url =>
new Promise(resolve => {
const img = new Image();
img.onload = e => resolve(img);
img.src = url;
})
);
Promise
.all(assetsLoaded)
.then(images => {
(function gameLoop() {
requestAnimationFrame(gameLoop);
ctx.clearRect(0, 0, canvas.width, canvas.height);
images.forEach((e, i) =>
ctx.drawImage(
e,
i * 150 + Math.cos(Date.now() * 0.003 + i) * 20, // x
Math.sin(Date.now() * 0.005 + i) * 50 + 50 // y
)
);
})();
})
.catch(err => console.error(err))
;
// from https://stackoverflow.com/a/61337279/3807365
Specifically for your question, you can declare a global object var cache={} where its keys are the src of images to load. so, for example:
var cache = {};
LoadSprite(src, position) {
if (cache[src]) {
this.ctx.drawImage(cache[src], ...position, true);
return;
}
const _image = new Image();
_image.src = src;
_image.onload = () => {
cache[src] = _image;
this.ctx.drawImage(_image, ...position, true);
}
}
Related
I want to record the multiple layered canvas using MediaRecorder.
But, i don't know how to achieve it...
help me...
this is the my pseudo code
const RECORD_FRAME = 30;
const canvasVideoTrack = canvas.captureStream(RECORD_FRAME).getVideoTracks()[0];
const waterMarkCanvasTrack = waterMarkCanvas.captureStream(RECORD_FRAME).getVideoTracks()[0];
const stream= new MediaStream();
const mediaRecorder = new MediaRecorder(stream);
mediaRecorder.stream.addTrack(canvasVideoTrack)
mediaRecorder.stream.addTrack(waterMarkCanvasTrack)
// .... recording
You need to draw all your canvases on a single one.
Even if we could record multiple video streams (which we can't yet), what you need is to composite these video streams. And for this, you use a canvas:
const prepareCanvasAnim = (color, offset) => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = color;
let x = offset;
const anim = () => {
x = (x + 1) % canvas.width;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(x, offset, 50, 50);
requestAnimationFrame(anim);
}
anim();
return canvas;
}
const canvas1 = prepareCanvasAnim("red", 20);
const canvas2 = prepareCanvasAnim("green", 80);
document.querySelector(".container").append(canvas1, canvas2);
const btn = document.querySelector("button");
const record = (evt) => {
btn.textContent = "Stop Recording";
btn.disabled = true;
setTimeout(() => btn.disabled = false, 5000); // at least 5s recording
// prepare our merger canvas
const canvas = canvas1.cloneNode();
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#FFF";
const toMerge = [canvas1, canvas2];
const anim = () => {
ctx.fillRect(0, 0, canvas.width, canvas.height);
toMerge.forEach(layer => ctx.drawImage(layer, 0, 0));
requestAnimationFrame(anim);
};
anim();
const stream = canvas.captureStream();
const chunks = [];
const recorder = new MediaRecorder(stream);
recorder.ondataavailable = (evt) => chunks.push(evt.data);
recorder.onstop = (evt) => exportVid(chunks);
btn.onclick = (evt) => recorder.stop();
recorder.start();
};
function exportVid(chunks) {
const vid = document.createElement("video");
vid.controls = true;
vid.src = URL.createObjectURL(new Blob(chunks));
document.body.append(vid);
btn.onclick = record;
btn.textContent = "Start Recording";
}
btn.onclick = record;
canvas { border: 1px solid }
.container canvas { position: absolute }
.container:hover canvas { position: relative }
.container { height: 180px }
<div class="container">Hover here to "untangle" the canvases<br></div>
<button>Start Recording</button><br>
But if you're going this way anyway, you might just as well do the merging from the get-go and append a single canvas in the document, the browser's compositor will have less work to do.
Hello everyone!
Is it possible to create image with JavaScript then render shapes on it, Then draw it to the game canvas?
Without using dataurl, url, or src, on any of that!
var ctx = document.getElementById("canvas").getContext("2d");
var img = new Image();
// TODO: Draw stuff to the image img
function game() {
window.requestAnimationFrame(game);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(img, 0, 0, 256, 256);
}
window.requestAnimationFrame(game);
The CanvasRenderingContext2D.drawImage() function can take an multiple types of images as a source, including another Canvas. The example below shows that an image is loaded in the first canvas. Then you can draw on it by holding down the mouse and moving it. When you release the second canvas will draw an image of the first canvas as it is at that moment.
And all the magic is just in the last function.
contextTwo.drawImage(canvasOne, 0, 0, 256, 256);
const canvasOne = document.getElementById('canvas1');
const canvasTwo = document.getElementById('canvas2');
const contextOne = canvasOne.getContext('2d');
const contextTwo = canvasTwo.getContext('2d');
canvasOne.width = 256;
canvasOne.height = 256;
canvasTwo.width = 256;
canvasTwo.height = 256;
const canvasBounds = canvasOne.getBoundingClientRect();
let mouseData = {
isClicked: false,
position: [0, 0],
}
const onMouseDown = event => {
mouseData.isClicked = true;
render();
};
const onMouseMove = ({ clientX, clientY }) => {
const x = clientX - canvasBounds.left;
const y = clientY - canvasBounds.top;
mouseData.position = [x, y];
render();
};
const onMouseUp = event => {
mouseData.isClicked = false;
render();
moveImage();
};
function setup() {
const img = new Image();
img.src = '//picsum.photos/256/256'
img.onload = function() {
contextOne.drawImage(img, 0, 0, 256, 256);
}
canvasOne.addEventListener('mousedown', onMouseDown);
canvasOne.addEventListener('mousemove', onMouseMove);
canvasOne.addEventListener('mouseup', onMouseUp);
}
function render() {
requestAnimationFrame(() => {
const { isClicked, position } = mouseData;
const [ x, y ] = position;
if (isClicked) {
contextOne.beginPath();
contextOne.arc(x, y, 5, 0, Math.PI * 2)
contextOne.fillStyle = 'red'
contextOne.fill();
contextOne.closePath();
}
});
}
function moveImage() {
contextTwo.drawImage(canvasOne, 0, 0, 256, 256);
}
setup();
body {
display: flex;
}
canvas {
width: 256px;
height: 256px;
border: 1px solid #d0d0d0;
}
<canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas>
I run out of ideas.. I saw this question got asked many times here and here and many more, but I could not find a way to solve my issue. So as I understood, the problem is this it does not refer to the correct context. How do I use an arrow function to capture this from the declaration site?
drawImageProp(ctx, img, x, y, w, h, offsetX, offsetY) {
// more code than displayed here
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}
onFileSelected(event) {
for (const file of event.target.files) {
if (file) {
const reader = new FileReader();
reader.onload = function(e: FileReaderEvent) {
const canvas = <HTMLCanvasElement>document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const img = new Image;
img.onload = draw;
function draw() {
this.drawImageProp(ctx, this, 0, 0, canvas.width, canvas.height, 0.5, 0.5);
}
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
}
}
Use a closure:
onFileSelected(event) {
const self = this;
for (const file of event.target.files) {
if (file) {
const reader = new FileReader();
reader.onload = function(e: FileReaderEvent) {
const canvas = <HTMLCanvasElement>document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const img = new Image;
img.onload = draw;
function draw() {
self.drawImageProp(ctx, img, 0, 0, canvas.width, canvas.height, 0.5, 0.5);
}
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
}
}
I'm wondering what I am missing here I have a code that checks if the image is transparent based on canvas.
function Trasparent(url, npc, clb) {
var img = new Image();
img.src = url;
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
var maxlength = Math.sqrt(img.width * img.height) * 5 + 300;
if (canvas.toDataURL().length < maxlength) {
clb(false, npc);
} else {
clb(true, npc);
}
};
}
When I'm doing it this way:
function Trasparent(url, npc, clb) {
var img = new Image();
img.src = url;
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
var maxlength = Math.sqrt(img.width * img.height) * 5 + 300;
if (canvas.toDataURL().length < maxlength) {
clb(false, npc);
} else {
clb(true, npc);
}
};
}
function callback(success, npc) {
if (success) {
console.log("Not trasparent");
} else {
console.log("Trasparent");
}
}
Trasparent(npc.icon, npc, callback);
It works just fine, but when I'm trying to make this function above like this:
function Trasparent(url, npc) {
var img = new Image();
img.src = url;
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
var maxlength = Math.sqrt(img.width * img.height) * 5 + 300;
if (canvas.toDataURL().length < maxlength) {
return false;
} else {
return true;
}
};
}
if(Transparent(npc.icon, npc)){
console.log("Not transparent");
} else {
console.log("Trasparent");
}
It doesn't work...
Even tho in this example which i wrote it works fine:
function check(a, b) {
var result = a + b;
if (result <= 10) {
return (false);
} else {
return (true);
}
}
function test() {
if (check(5, 4)) {
console.log(">10");
} else {
console.log("<10")
}
}
test();
What i am missing?
The return-statements are not belonging to the function Transparent!
You are creating a different function here which returns true or false when it is called, but it is not executed right away and it's return-value is not returned in your function Transparent.
What you have is essentially this snippet:
function Trasparent(url, npc) {
var img = new Image();
img.src = url;
img.onload = function() {
// function body totally unrelated to the Transparent-function
// and not executed and not returning anything right now
};
return undefined;
}
(these are not actually the same since fat arrow functions capture this, see What does "this" refer to in arrow functions in ES6?)
Your solution with the callback is the way to go.
your code is async. you cant return true or false. you need to return a callback thats why your if doesn't work
function Trasparent(url, npc,cb) {
var img = new Image();
img.src = url;
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
var maxlength = Math.sqrt(img.width * img.height) * 5 + 300;
if (canvas.toDataURL().length < maxlength) {
cb(false);
} else {
cb(true);
}
};
}
Transparent(npc.icon, npc,function(result){
if(result)
console.log("Not transparent");
} else {
console.log("Trasparent");
}
})
)
Here is the code:
var spriteFolder = "../../assets/Painter/sprites/";
var sprites = {};
sprites.background = new Image();
sprites.background.src = spriteFolder + "spr_background.jpg";
sprites.cannon_barrel = new Image();
sprites.cannon_barrel.src = spriteFolder + "spr_cannon_barrel.png";
sprites.cannon_red = new Image();
sprites.cannon_red.src = spriteFolder + "spr_cannon_red.png";
sprites.cannon_green = new Image();
sprites.cannon_green.src = spriteFolder + "spr_cannon_green.png";
sprites.cannon_blue = new Image();
sprites.cannon_blue.src = spriteFolder + "spr_cannon_blue.png";
var Canvas2D = {
canvas: undefined,
canvasContext: undefined
};
Canvas2D.initialize = function(canvasName) {
Canvas2D.canvas = document.getElementById(canvasName);
Canvas2D.canvasContext = Canvas2D.canvas.getContext("2d");
};
Canvas2D.clear = function() {
Canvas2D.canvas.clearRect(0, 0, Canvas2D.canvas.width, Canvas2D.canvas.height);
};
Canvas2D.drawImage = function(sprite, position, rotation, origin) {
Canvas2D.canvasContext.save();
Canvas2D.canvasContext.translate(position.x, position.y);
Canvas2D.canvasContext.rotate(rotation);
Canvas2D.canvasContext.drawImage(sprite,
0, 0, sprite.width, sprite.height,
-origin.x, -origin.y, sprite.width, sprite.height
);
Canvas2D.canvasContext.restore();
};
function init() {
Canvas2D.initialize("myCanvas");
setTimeout(function() {
Canvas2D.canvasContext.drawImage(sprites.background, 0, 0);
}, 1000);
}
document.addEventListener("DOMContentLoaded", init);
If I don't use setTimeout, then the image is not drawn. Why?
Because your images were not yet loaded.
Thanks to your 1sec timeout they have enough time to load.
Have a look at this example from MDN https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images
You need to use similar approach - see img.onload()
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image();
img.onload = function(){
ctx.drawImage(img,0,0);
ctx.beginPath();
ctx.moveTo(30,96);
ctx.lineTo(70,66);
ctx.lineTo(103,76);
ctx.lineTo(170,15);
ctx.stroke();
};
img.src = 'https://mdn.mozillademos.org/files/5395/backdrop.png';
}