如何在2D Canvas中仅绘制图像的非透明瓦片区域?
解决Canvas瓦片地图只渲染非透明瓦片的问题
核心思路
先检测每个裁剪出的瓦片是否包含非透明像素,只保留有效瓦片,避免渲染全透明的无用瓦片,最终仅保留你需要的4个有效图像,以此提升渲染性能。
具体实现方法
1. 编写瓦片透明度检测函数
创建一个辅助函数,用来判断单个瓦片是否包含非透明内容:
async function isTileNonTransparent(tileBmp) { const tempCanvas = document.createElement('canvas'); tempCanvas.width = tileBmp.width; tempCanvas.height = tileBmp.height; const tempCtx = tempCanvas.getContext('2d'); tempCtx.drawImage(tileBmp, 0, 0); const imageData = tempCtx.getImageData(0, 0, tileBmp.width, tileBmp.height); const data = imageData.data; // 遍历像素数据,检查是否有alpha值大于0的像素 for (let i = 3; i < data.length; i += 4) { if (data[i] > 0) { return true; } } return false; }
2. 修改原代码,只保留有效瓦片
在生成每个瓦片后,先调用检测函数,只有非透明的瓦片才加入数组:
(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); // 检测瓦片是否非透明,是则加入数组 if (await isTileNonTransparent(bmp)) { tiles.push({ bmp, x, y }); } else { // 释放透明瓦片的内存 bmp.close(); } } console.log(tiles); // 现在只会输出4个有效瓦片 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); }); } draw(); })();
性能优化(可选)
上面的方法每个瓦片都要创建临时canvas,如果想进一步提升效率,可以一次性获取整个大图的像素数据,批量检测所有瓦片:
(async () => { const img = new Image(); img.src = "./img/obstacles.png"; await img.decode(); let tiles = []; let tileWidth = 32; let tileHeight = 32; const totalWidth = img.width; const totalHeight = img.height; // 创建临时canvas获取整个大图的像素数据 const tempCanvas = document.createElement('canvas'); tempCanvas.width = totalWidth; tempCanvas.height = totalHeight; const tempCtx = tempCanvas.getContext('2d'); tempCtx.drawImage(img, 0, 0); const fullImageData = tempCtx.getImageData(0, 0, totalWidth, totalHeight); const data = fullImageData.data; for (let i = 0; i < 100; i++) { let tileX = i % 10; let tileY = Math.floor(i / 10); let startX = tileX * tileWidth; let startY = tileY * tileHeight; let isNonTransparent = false; // 遍历当前瓦片区域的所有像素 for (let y = startY; y < startY + tileHeight; y++) { for (let x = startX; x < startX + tileWidth; x++) { const index = (y * totalWidth + x) * 4; if (data[index + 3] > 0) { isNonTransparent = true; // 找到非透明像素就跳出所有循环 y = startY + tileHeight; x = startX + tileWidth; } } } if (isNonTransparent) { let bmp = await createImageBitmap(img, startX, startY, tileWidth, tileHeight); tiles.push({ bmp, x: tileX, y: tileY }); } } 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); }); } draw(); })();
这种方法只创建一次临时canvas,减少了重复创建DOM元素的开销,对于大量瓦片的场景更高效。
内容的提问来源于stack exchange,提问作者anathrax
相关产品推荐
相关产品推荐

