前端融合图片mask
之前实现了tif文件的融合,现在实现图片的融合,效果如下
第一张是融合右边两张图的结果
我的思路是:
- 初始使用canvas加载原图,此时未显示标注
- 点击显示标注后,将原图和mask图传给worker
- worker接受数据后,转为blob格式再经过createImageBitmap处理拿到宽高
- 接着使用OffscreenCanvas渲染原地形图
- 再创建一个OffscreenCanvas处理地形mask图
- 将该地形mask的imageData值拿到,循环并处理对应的颜色
- 把处理后的mask图重新绘制回去
- 将处理后的mask的OffscreenCanvas绘制在地形图OffscreenCanvas上
- 最后将最新的地形图OffscreenCanvas进行转换返回给主线程
这是index.html代码:
<!DOCTYPE html>
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title></title><style type="text/css">* {margin: 0;padding: 0;}h1 {position: absolute;top: 0;z-index: 99;color: red;}</style>
</head><body><input id="checkSVGLabel" type="checkbox" name=""> 显隐标注<br><!-- <img id="defaultImg" src="" alt=""> --><canvas id="canvas"></canvas><script type="text/javascript">// const terrainImageUrl = 'http://192.168.10.159:5502/images/orz.png'; // 替换为你的地形图URLconst terrainImageUrl = 'http://192.168.10.159:5502/images/orz5.png'; // 替换为你的地形图URLconst maskImageUrl = 'http://192.168.10.159:5502/images/mask1.png'; // 替换为你的黑底白框图URL// const terrainImageUrl = 'http://192.168.10.159:5502/images/orz3.jpg'; // 替换为你的地形图URL// const maskImageUrl = 'http://192.168.10.159:5502/images/mask4.jpg'; // 替换为你的黑底白框图URLconst canvas = document.getElementById('canvas');const ctx = canvas.getContext('2d');const checkSVGLabel = document.getElementById('checkSVGLabel');// const defaultImg = document.getElementById('defaultImg');let changeImgUrl = terrainImageUrllet imageWorker = nullfunction handleMaskImg (e){if (e.target.checked && imageWorker) {mergeMaskImg(terrainImageUrl, maskImageUrl, 'mergeMask')} else {mergeMaskImg(terrainImageUrl, maskImageUrl)}}// 融合色块function mergeMaskImg (orz, mask, type){if (type === 'mergeMask') {imageWorker.postMessage({orz,mask,type: 'creatMaskImg'})} else {const image = new Image();image.src = orz;image.onload = function (){canvas.width = image.width;canvas.height = image.height;ctx.drawImage(image, 0, 0);}}}function clearWorker (){if (worker) {worker.terminate()}worker = null}window.onload = function (){mergeMaskImg(terrainImageUrl, maskImageUrl)imageWorker = new Worker("../js/imageWorker.js")imageWorker.onmessage = (e) =>{const { type } = e.data;if (type === 'putMaskImg') {// 读取影像// defaultImg.src = e.data.dataUrl// 使用画布console.log(e)const { imageBitmap, canvasWidth, canvasHeight } = e.data;canvas.width = canvasWidth;canvas.height = canvasHeight;ctx.drawImage(imageBitmap, 0, 0, canvasWidth, canvasHeight);}}}checkSVGLabel.addEventListener('change', handleMaskImg);</script>
</body></html>
这是worker代码
self.addEventListener('message', async ({ data }) => {if (data.type === 'creatMaskImg') {let { orz, mask } = data// 加载地形图和蒙版图像const [terrainImage, maskImage] = await Promise.all([loadImageBitmap(orz),loadImageBitmap(mask)])const canvasWidth = terrainImage.widthconst canvasHeight = terrainImage.heightconst offscreenCanvas = new OffscreenCanvas(canvasWidth, canvasHeight)const ctx = offscreenCanvas.getContext('2d')// 绘制地形图ctx.drawImage(terrainImage, 0, 0, canvasWidth, canvasHeight)// 创建一个临时canvas来处理蒙版const tempCanvas = new OffscreenCanvas(maskImage.width,maskImage.height)const tempCtx = tempCanvas.getContext('2d')// 将黑底白框图绘制到临时canvastempCtx.drawImage(maskImage, 0, 0)// 获取图像数据const maskImageData = tempCtx.getImageData(0,0,tempCanvas.width,tempCanvas.height)const imgData = maskImageData.datafor (let i = 0; i < imgData.length; i += 4) {// 获取当前像素的RGB值const red = imgData[i]const green = imgData[i + 1]const blue = imgData[i + 2]if (red === 0 && green === 0 && blue === 0) {imgData[i + 3] = 0 // 透明度设置为0} else {if (red === 255 && green === 255 && blue === 255) {// 黑色// imgData[i] = 0// imgData[i + 1] = 0// imgData[i + 2] = 0// 紫色imgData[i] = 254imgData[i + 1] = 0imgData[i + 2] = 255imgData[i + 3] = imgData[i + 3] * 0.5// 透明图// imgData[i] = null// imgData[i + 1] = null// imgData[i + 2] = null}}}// 将处理后的蒙版图像数据放回临时canvastempCtx.putImageData(maskImageData, 0, 0)// 将临时canvas上的蒙版绘制到地形图上ctx.drawImage(tempCanvas, 0, 0, canvasWidth, canvasHeight)// ----------------- 传blob图像数据// 将处理后的 offscreenCanvas 转换为 Blob// const blob = await offscreenCanvas.convertToBlob()// const dataUrl = await blobToDataURL(blob)// self.postMessage({// dataUrl,// canvasWidth,// canvasHeight,// type: 'putMaskImg'// })// ----------------- 传canvas数据// 将处理后的 offscreenCanvas 转换为 ImageBitmap 并返回到主线程const imageBitmap = offscreenCanvas.transferToImageBitmap()self.postMessage({ imageBitmap, canvasWidth, canvasHeight, type: 'putMaskImg' },[imageBitmap])}
})async function loadImageBitmap(url) {const response = await fetch(url)const blob = await response.blob()return createImageBitmap(blob)
}function blobToDataURL(blob) {return new Promise((resolve, reject) => {const reader = new FileReader()reader.onloadend = () => resolve(reader.result)reader.onerror = rejectreader.readAsDataURL(blob)})
}
在worker中其实还可以将mask图片的黑色区域进行转换,也可以设置透明值,非常方便,如果不需要复杂的颜色处理,在处理后还可以结合mask-image去实现蒙版效果。