淡出画布图像中的瓦片



仍在学习画布。我如何淡出图像中的每个平铺,我有一个数组中的图像数据:

for (let i = 0; i < tilesY; i++) {
for (let j = 0; j < tilesX; j++) {
this.ctxTile.putImageData(this.tileData[itr], j * tileWidth, i * tileHeight);
itr += 1
}
}

我知道解决方案一定与合成有关。我想让每个贴图单独淡出。putImageData可以工作,并且图像位于画布内,并组装为一组tile。

感谢

通常你只需要在绘制上下文时使用ctx.globalAlpha属性来设置tile的alpha值。然而,putImageData在API中是一种奇怪的野兽,因为它忽略了上下文转换,剪切区域和在我们的情况下的合成规则,包括globalAlpha

所以一种方法是"erase"在我们画好给定的贴图之后。为此,我们可以使用globalCompositeOperation = "destination-out"属性,我们将在调用简单的fillRect()时使用我们想要的反向globalAlpha。(幸运的是putImageData总是只画矩形)。

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const tileWidth  = 50;
const tileHeight = 50;
class Tile {
constructor(x, y, width, height) {
this.x = Math.round(x); // putImageData only renders at integer coords
this.y = Math.round(y);
this.width = width;
this.height = height;
this.img = buildImageData(width, height);
this.alpha = 1;
}
isPointInPath(x, y) {
return x >= this.x && x <= this.x + this.width &&
y >= this.y && y <= this.y + this.height;
}
draw() {
ctx.putImageData(this.img, this.x, this.y);
ctx.globalAlpha = 1 - this.alpha; // inverse alpha
// the next drawing will basically erase what it represents
ctx.globalCompositeOperation = "destination-out";
ctx.fillRect(this.x, this.y, this.width, this.height);
// restore the context
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
}
}
const tiles = Array.from({ length: 5 }, (_, i) => new Tile(i * tileWidth * 1.25, 0, tileWidth, tileHeight));
canvas.onclick = (e) => {
const x = e.clientX - canvas.offsetLeft;
const y = e.clientY - canvas.offsetTop;
const clickedTile = tiles.find((tile) => tile.isPointInPath(x, y));
if (clickedTile) { clickedTile.alpha -= 0.1 };
redraw();
};
redraw();
function redraw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
tiles.forEach((tile) => tile.draw());
}
function buildImageData(width, height) {
const img = new ImageData(width, height);
const arr = new Uint32Array(img.data.buffer);
for (let i = 0; i < arr.length; i++) {
arr[i] = Math.random() * 0xFFFFFF + 0xFF000000;
}
return img;
}
<p>Click on each tile to lower its alpha.</p>
<canvas></canvas>

然而,这意味着对于每个贴图,我们有一个putImageData+一个合成fillRect。如果你有很多贴图,那就会造成相当大的开销。

因此,最好的方法可能是将所有ImageData对象转换为ImageBitmap对象。为了理解两者之间的区别,我邀请你阅读我的回答。

一旦我们有了ImageBitmaps,我们可以直接在draw调用上应用globalAlpha:

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const tileWidth  = 50;
const tileHeight = 50;
class Tile {
constructor(x, y, width, height) {
this.x = Math.round(x); // putImageData only renders at integer coords
this.y = Math.round(y);
this.width = width;
this.height = height;
this.alpha = 1;
const imgData = buildImageData(width, height);
// createImageBitmap is "async"
this.ready = createImageBitmap(imgData)
.then((bmp) => this.img = bmp);
}
isPointInPath(x, y) {
return x >= this.x && x <= this.x + this.width &&
y >= this.y && y <= this.y + this.height;
}
draw() {
// single draw per tile
ctx.globalAlpha = this.alpha;
ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
ctx.globalAlpha = 1;
}
}
const tiles = Array.from({ length: 5 }, (_, i) => new Tile(i * tileWidth * 1.25, 0, tileWidth, tileHeight));
canvas.onclick = (e) => {
const x = e.clientX - canvas.offsetLeft;
const y = e.clientY - canvas.offsetTop;
const clickedTile = tiles.find((tile) => tile.isPointInPath(x, y));
if (clickedTile) { clickedTile.alpha -= 0.1 };
redraw();
};
// wait for all the ImageBitmaps are generated
Promise.all(tiles.map((tile) => tile.ready)).then(redraw);
function redraw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
tiles.forEach((tile) => tile.draw());
}
function buildImageData(width, height) {
const img = new ImageData(width, height);
const arr = new Uint32Array(img.data.buffer);
for (let i = 0; i < arr.length; i++) {
arr[i] = Math.random() * 0xFFFFFF + 0xFF000000;
}
return img;
}
<p>Click on each tile to lower its alpha.</p>
<canvas></canvas>

最新更新