简体   繁体   English

淡出画布图像中的图块

[英]Fade out tiles in canvas image

Still learning Canvas.还在学习画布。 How can I fade out each tile in an image that I have as Image Data in an array:如何淡出作为数组中图像数据的图像中的每个图块:

            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
                }
            }

I understand that the solution must have something to do with compositing?我知道解决方案必须与合成有关? I want to fade out each tile individually.我想单独淡出每个图块。 The putImageData works and the image is inside canvas, and assembled as a set of tiles. putImageData 有效,图像位于画布内,并组装为一组图块。

Thanks谢谢

Usually you'd just play with the ctx.globalAlpha property at the time of drawing to your context to set your tile's alpha.通常,您只需在绘制到上下文时使用ctx.globalAlpha属性来设置图块的 alpha。 However, putImageData is kind of a strange beast in the API in that it ignores the context transformation, clipping areas and in our case compositing rules, including globalAlpha .然而, putImageData在 API 中是一种奇怪的野兽,因为它忽略了上下文转换、剪切区域以及在我们的例子中包括globalAlpha在内的合成规则。

So one hack around would be to "erase" the given tile after we've drawn it.因此,一种解决方法是在我们绘制给定图块后“擦除”它。 For this we can use the globalCompositeOperation = "destination-out" property that we'll use on a call to a simple fillRect() with the inverse globalAlpha we want.为此,我们可以使用globalCompositeOperation = "destination-out"属性,我们将在调用带有我们想要的逆globalAlpha的简单fillRect()时使用该属性。 (Luckily putImageData always draws only rectangles). (幸运的是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>

However this means that for each tile we have one putImageData + one composited fillRect .然而,这意味着对于每个图块,我们有一个putImageData + 一个复合的fillRect If you've got a lot of tiles, that makes for a pretty big overhead.如果你有很多瓷砖,那会产生相当大的开销。

So instead the best might be to convert all your ImageData objects to ImageBitmap ones.因此,最好的办法可能是将所有ImageData对象转换为ImageBitmap对象。 To understand the difference between both I invite you to read this answer of mine .要了解两者之间的区别,我邀请您阅读我的这个答案

Once we have ImageBitmaps, we can apply the globalAlpha on our draw call directly:一旦我们有了 ImageBitmaps,我们就可以直接在我们的绘制调用上应用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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM