I would like to draw this image on canvas without the transparent parts. The principle of my rendering is that I crop small images from a large image using the createImageBitmap method and store them in an array. I then render them on the canvas one by one. The problem is that it also unnecessarily draws the transparent parts. So if I log my array I get this. Since my map is 10x10 tiles it results in 100 images (of which 96 are useless). Instead, I would like to save only 4 images.
Not only that it messes up my performance but I have more reasons why it bothers me. Is there a way to solve this problem?
My code so far:
(async () => {
const img = new Image();
img.src = "./img/obstacles.png";
await img.decode();
let tiles = [];
let tileWidth = 32;
let tileHeight = 32;
for (let i = 0; i < 100; i++) {
let x = i % 10;
let y = Math.floor(i / 10);
let bmp = await createImageBitmap(img, x * tileWidth, y * tileHeight, tileWidth, tileHeight); // index.js:13
tiles.push({
bmp,
x,
y
})
}
console.log(tiles)
const canvas = document.querySelector("canvas");
canvas.width = 320;
canvas.height = 320;
const ctx = canvas.getContext("2d");
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
tiles.forEach((tile) => {
ctx.drawImage(tile.bmp, tile.x * tileWidth, tile.y * tileHeight);
})
// requestAnimationFrame(draw)
}
draw();
})();
The best is to prepare your asset correctly, so here that would mean having only the 3 unique sprites in your atlas:
Then you can use your simple loop to get these as separate objects.
However it's not uncommon to have sprites of different sizes in the same atlas, in such a case, your simple loop won't do. Instead, you should prepare a coordinates dictionary (e.g as a JSON file, or embedded directly in your js), with all the coordinates of each sprites.
Most spritesheet generating tools will produce this for you.
Here is an example where I just added one big sprite to the atlas:
(async () => {
// If you are same-origin, it's better to fetch as a Blob
// and create your ImageBitmap from the Blob
// Here we aren't, so we have to go through an <img>
const img = new Image();
img.src = "https://i.sstatic.net/h7w1C.png";
await img.decode();
document.body.append(img);
// We hardcode the coords of each sprite
const map = [
// [x, y, w, h]
[0, 0, 32, 32],
[0, 32, 32, 32],
[0, 64, 32, 32],
[32, 0, 96, 96],
];
const tiles = [];
for (const [x, y, w, h] of map) {
const bmp = await createImageBitmap(img, x, y, w, h);
tiles.push({
bmp,
x,
y
})
}
console.log(tiles)
const canvas = document.querySelector("canvas");
canvas.width = 320;
canvas.height = 320;
const ctx = canvas.getContext("2d");
let m = 0;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
m = (m + 0.005) % 0.5;
const margin = m + 1;
tiles.forEach((tile) => {
// we add some margin to show these are separate bitmaps
ctx.drawImage(tile.bmp, tile.x * margin, tile.y * margin);
})
requestAnimationFrame(draw)
}
draw();
})().catch(console.error);
.as-console-wrapper { max-height: 100px !important }
<canvas></canvas>