图像非透明部分的边框

如何在带有 alpha 通道的图像周围绘制轮廓?

这个演示展示了如何使用 Konva 框架的自定义滤镜。

如需了解有关自定义滤镜的更多信息,请查看 自定义滤镜教程

在这个演示中,我们将创建一个自定义滤镜,它将在图像的轮廓周围绘制一个实心边框。
由于跟随轮廓是一件复杂的事情,我们将采用一种方法。我们将使用模糊的阴影作为边框基础。
我们将把透明/模糊的像素替换为我们想要用作边框的实色。

有关所有可用滤镜,请访问 滤镜文档

Konva Custom Filter Image Demoview raw
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/konva@9.3.18/konva.min.js"></script>
<meta charset="utf-8" />
<title>Konva Border Image Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
</style>
</head>

<body>
<div id="container"></div>
<script>
var stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});

var layer = new Konva.Layer();
stage.add(layer);

Konva.Image.fromURL('/assets/lion.png', function (image) {
layer.add(image);
image.setAttrs({
x: 80,
y: 30,
borderSize: 5,
borderColor: 'red',
});

image.filters([Border]);
image.cache();
});

// now we will define our border filter

var canvas = document.createElement('canvas');
var tempCanvas = document.createElement('canvas');

// make all pixels opaque 100% (except pixels that 100% transparent)
function removeTransparency(canvas) {
var ctx = canvas.getContext('2d');

var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var nPixels = imageData.data.length;
for (var 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) {
var nPixels = imageData.data.length;

var size = this.getAttr('borderSize') || 0;

// - first set correct dimensions for canvases
canvas.width = imageData.width;
canvas.height = imageData.height;

tempCanvas.width = imageData.width;
tempCanvas.height = imageData.height;

// - the draw original shape into temp canvas
tempCanvas.getContext('2d').putImageData(imageData, 0, 0);

// - then we need to remove alpha chanel, because it will affect shadow (transparent shapes has smaller shadow)
removeTransparency(tempCanvas);

var ctx = canvas.getContext('2d');
var color = this.getAttr('borderColor') || 'black';

// 3. we will use shadow as border
// so we just need apply shadow on the original image
ctx.save();
ctx.shadowColor = color;
ctx.shadowBlur = size;
ctx.drawImage(tempCanvas, 0, 0);
ctx.restore();

// - Then we will dive in into image data of [original image + shadow]
// and remove transparency from shadow
var tempImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

var SMOOTH_MIN_THRESHOLD = 3;
var SMOOTH_MAX_THRESHOLD = 10;

let val, hasValue;

var offset = 3;

for (var i = 3; i < nPixels; i += 4) {
// skip opaque pixels
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;
}

// draw resulted image (original + shadow without opacity) into canvas
ctx.putImageData(tempImageData, 0, 0);

// then fill whole image with color (after that shadow is colored)
ctx.save();
ctx.globalCompositeOperation = 'source-in';
ctx.fillStyle = color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();

// then we need to copy colored shadow into original imageData
var newImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

var indexesToProcess = [];
for (var i = 3; i < nPixels; i += 4) {
var hasTransparentOnTop =
imageData.data[i - imageData.width * 4 * offset] === 0;
var hasTransparentOnTopRight =
imageData.data[i - (imageData.width * 4 + 4) * offset] === 0;
var hasTransparentOnTopLeft =
imageData.data[i - (imageData.width * 4 - 4) * offset] === 0;
var hasTransparentOnRight = imageData.data[i + 4 * offset] === 0;
var hasTransparentOnLeft = imageData.data[i - 4 * offset] === 0;
var hasTransparentOnBottom =
imageData.data[i + imageData.width * 4 * offset] === 0;
var hasTransparentOnBottomRight =
imageData.data[i + (imageData.width * 4 + 4) * offset] === 0;
var hasTransparentOnBottomLeft =
imageData.data[i + (imageData.width * 4 - 4) * offset] === 0;
var hasTransparentAround =
hasTransparentOnTop ||
hasTransparentOnRight ||
hasTransparentOnLeft ||
hasTransparentOnBottom ||
hasTransparentOnTopRight ||
hasTransparentOnTopLeft ||
hasTransparentOnBottomRight ||
hasTransparentOnBottomLeft;

// if pixel presented in original image - skip it
// because we need to change only shadow area
if (
imageData.data[i] === 255 ||
(imageData.data[i] && !hasTransparentAround)
) {
continue;
}
if (!newImageData.data[i]) {
// skip transparent pixels
continue;
}
indexesToProcess.push(i);
}

for (var index = 0; index < indexesToProcess.length; index += 1) {
var i = indexesToProcess[index];

var alpha = imageData.data[i] / 255;

if (alpha > 0 && alpha < 1) {
var aa = 1 + 1;
}
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;

if (newImageData.data[i] < 255 && alpha > 0) {
var bb = 1 + 1;
}
}
}
</script>
</body>
</html>