围绕非透明部分的图像边框
如何在具有 alpha 通道的图像周围绘制边框?
此演示展示了如何使用 Konva 框架的自定义滤镜创建一个跟随带有 alpha 通道的图像轮廓的边框。
由于准确跟随轮廓是一项复杂的任务,我们将使用模糊阴影作为边框基础的技术。该滤镜用我们希望用于边框的实心颜色替换透明/模糊的像素。
说明: 观察带有自定义边框的图像,该边框跟随其非透明部分。
import Konva from 'konva'; // 创建舞台 const stage = new Konva.Stage({ container: 'container', width: window.innerWidth, height: window.innerHeight, }); const layer = new Konva.Layer(); stage.add(layer); // 定义我们的自定义滤镜的变量 let canvas = document.createElement('canvas'); let tempCanvas = document.createElement('canvas'); // 使所有像素变为不透明 100%(除了不透明像素) function removeTransparency(canvas) { const ctx = canvas.getContext('2d'); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const nPixels = imageData.data.length; for (let i = 3; i < nPixels; i += 4) { if (imageData.data[i] > 0) { imageData.data[i] = 255; } } ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.putImageData(imageData, 0, 0); return canvas; } // 定义我们的自定义边框滤镜 function Border(imageData) { const nPixels = imageData.data.length; const size = this.getAttr('borderSize') || 0; // 设置画布的正确尺寸 canvas.width = imageData.width; canvas.height = imageData.height; tempCanvas.width = imageData.width; tempCanvas.height = imageData.height; // 将原始形状绘制到临时画布中 tempCanvas.getContext('2d').putImageData(imageData, 0, 0); // 移除 alpha 通道,因为它会影响阴影(透明形状的阴影较小) removeTransparency(tempCanvas); const ctx = canvas.getContext('2d'); const color = this.getAttr('borderColor') || 'black'; // 使用阴影作为边框 ctx.save(); ctx.shadowColor = color; ctx.shadowBlur = size; ctx.drawImage(tempCanvas, 0, 0); ctx.restore(); // 获取 [原始图像 + 阴影] 的图像数据 const tempImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const SMOOTH_MIN_THRESHOLD = 3; const SMOOTH_MAX_THRESHOLD = 10; let val, hasValue; const offset = 3; for (let i = 3; i < nPixels; i += 4) { // 跳过不透明像素 if (imageData.data[i] === 255) { continue; } val = tempImageData.data[i]; hasValue = val !== 0; if (!hasValue) { continue; } if (val > SMOOTH_MAX_THRESHOLD) { val = 255; } else if (val < SMOOTH_MIN_THRESHOLD) { val = 0; } else { val = ((val - SMOOTH_MIN_THRESHOLD) / (SMOOTH_MAX_THRESHOLD - SMOOTH_MIN_THRESHOLD)) * 255; } tempImageData.data[i] = val; } // 将生成的图像(原始 + 无不透明度阴影)绘制到画布上 ctx.putImageData(tempImageData, 0, 0); // 用颜色填充整幅图像(之后阴影着色) ctx.save(); ctx.globalCompositeOperation = 'source-in'; ctx.fillStyle = color; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); // 将着色阴影复制到原始图像数据中 const newImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const indexesToProcess = []; for (let i = 3; i < nPixels; i += 4) { const hasTransparentOnTop = imageData.data[i - imageData.width * 4 * offset] === 0; const hasTransparentOnTopRight = imageData.data[i - (imageData.width * 4 + 4) * offset] === 0; const hasTransparentOnTopLeft = imageData.data[i - (imageData.width * 4 - 4) * offset] === 0; const hasTransparentOnRight = imageData.data[i + 4 * offset] === 0; const hasTransparentOnLeft = imageData.data[i - 4 * offset] === 0; const hasTransparentOnBottom = imageData.data[i + imageData.width * 4 * offset] === 0; const hasTransparentOnBottomRight = imageData.data[i + (imageData.width * 4 + 4) * offset] === 0; const hasTransparentOnBottomLeft = imageData.data[i + (imageData.width * 4 - 4) * offset] === 0; const hasTransparentAround = hasTransparentOnTop || hasTransparentOnRight || hasTransparentOnLeft || hasTransparentOnBottom || hasTransparentOnTopRight || hasTransparentOnTopLeft || hasTransparentOnBottomRight || hasTransparentOnBottomLeft; // 跳过原始图像中的像素 if (imageData.data[i] === 255 || (imageData.data[i] && !hasTransparentAround)) { continue; } if (!newImageData.data[i]) { // 跳过透明像素 continue; } indexesToProcess.push(i); } for (let index = 0; index < indexesToProcess.length; index += 1) { const i = indexesToProcess[index]; const alpha = imageData.data[i] / 255; imageData.data[i] = newImageData.data[i]; imageData.data[i - 1] = newImageData.data[i - 1] * (1 - alpha) + imageData.data[i - 1] * alpha; imageData.data[i - 2] = newImageData.data[i - 2] * (1 - alpha) + imageData.data[i - 2] * alpha; imageData.data[i - 3] = newImageData.data[i - 3] * (1 - alpha) + imageData.data[i - 3] * alpha; } } // 加载图像并应用滤镜 Konva.Image.fromURL('https://konvajs.org/assets/lion.png', function (image) { layer.add(image); image.setAttrs({ x: 80, y: 30, borderSize: 5, borderColor: 'red', }); image.filters([Border]); image.cache(); });