SVG或Canvas斜角和浮雕查找文本的中心线

时间:2018-10-07 00:42:44

标签: svg canvas

通过Photoshop的斜角和浮雕效果,可以轻松找到与文字字符中心线重合的中央“凸脊”。这可以通过增加适当的效果设置以最大程度地增大斜角来完成,从而创建这样的脊。

Photoshop example

此Photoshop示例经过处理,以进一步突出中心凸脊

在浏览器中使用SVG过滤器或Canvas技术是否可以达到相同的效果?

一旦有了这种效果,我就可以获取想要的中心线的坐标。

或者,是否存在一种通过数学手段从光栅图像或矢量形状中获得该中心线的算法?

2 个答案:

答案 0 :(得分:1)

尽管我不确定这是否是获得结果的有效方法,但不知何故,这让我很高兴。

中心线是多少?我将其定义为轮廓内所有满足以下条件的点的集合:必须至少有一条直线穿过该点,其中到最近的轮廓线的距离是沿着该点的直线的局部最大值。实际上,测试一条水平线和一条垂直线就足够了。

我尝试使用SVGGeometryElement接口中的两个功能来实现该功能:.getPointAtLength().isPointInFill()。到目前为止,第二个浏览器仅在Chrome中实现,因此这是唯一可以使用的浏览器。

<text>元素未实现SVGGeometryElement接口,因此必须将其转换为<path>。那是浏览器无法完成的,您需要一个合适的grafics程序。

查找,获取1000 * 500点,约为5000个点沿两个字母的轮廓是最近的,这是很多计算。因此,这包含了仅测试邻近区域中那些轮廓点的粗略机制。尽管如此,请花几秒钟来完成。如果仅以该大小计算一个字母并将画布大小减半,则执行时间将接近四分之一。

const width = 1000;
const height = 500;

const letter = document.querySelector('path');
const svg = document.querySelector('svg');
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';

function isInside(x, y) {
    const point = svg.createSVGPoint();
    point.x = x;
    point.y = y;
    return letter.isPointInFill(point);
}

// a 21 * 11 array of arrays
const fields = new Array(21).fill(0).map(() => {
    return new Array(11).fill(0).map(() => []);
});

// a list of points along the contour
const length = Math.floor(letter.getTotalLength());
Array.from(new Array(length), (x, i) => {
    return letter.getPointAtLength(i);
}).forEach(point => {
    // find out if a contour point is inside a 100 * 100 rectangle
    let rx1= Math.round(point.x / 100) * 2;
    let ry1 = Math.round(point.y / 100) * 2;
    // or a 100 * 100 rectangle that is offset by 50
    let rx2 = Math.round((point.x + 50) / 100) * 2 - 1;
    let ry2 = Math.round((point.y + 50) / 100) * 2 - 1;
    // push the point into all four lists for the rectangles it is part of
    fields[rx1][ry1].push(point);
    fields[rx1][ry2].push(point);
    fields[rx2][ry1].push(point);
    fields[rx2][ry2].push(point);
});

const data = new Float32Array(width * height);

for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
        // only handle points inside the contour
        if (isInside(x, y)) {
            // find out which 50 * 50 rectangle the inside point is part of
            const rx = Math.round(x / 50);
            const ry = Math.round(y / 50);
            // find the nearest contour point from the list for the
            // appropriate 100 * 100 rectangle
            const d = fields[rx][ry].reduce((min, point) => {
                const dist = Math.hypot(point.x - x, point.y - y)
                return Math.min(min, dist);
            }, 100);
            // store that distance value
            data[y * width + x] = d;
        }
    }
}

data.forEach((v, i, a) => {
    // find out if the distance to the nearest contour point
    // is a local maximum, vertically or horizontally
    const vert = a[i - width] < v && a[i + width] < v;
    const hor = a[i - 1] < v && a[i + 1] < v;
    if (vert || hor) {
        // color that point as part of the center line
        ctx.fillRect(i % width, Math.floor(i / width), 1, 1);
    }
});
<svg width="1000" height="500" style="position:absolute">
    <path id="letter" d="M 374.512,316.992 H 220.703 L 193.75,379.687 Q 183.789,402.832 183.789,414.258 183.789,423.34 192.285,430.371 201.074,437.109 229.785,439.16 V 450 H 104.688 V 439.16 Q 129.59,434.766 136.914,427.734 151.855,413.672 170.02,370.605 L 309.766,43.6523 H 320.02 L 458.301,374.121 Q 475,413.965 488.477,425.977 502.246,437.695 526.562,439.16 V 450 H 369.824 V 439.16 Q 393.555,437.988 401.758,431.25 410.254,424.512 410.254,414.844 410.254,401.953 398.535,374.121 Z M 366.309,295.312 298.926,134.766 229.785,295.312 Z M 810.742,247.266 Q 852.051,256.055 872.559,275.391 900.977,302.344 900.977,341.309 900.977,370.898 882.227,398.145 863.477,425.098 830.664,437.695 798.145,450 731.055,450 H 543.555 V 439.16 H 558.496 Q 583.398,439.16 594.238,423.34 600.977,413.086 600.977,379.687 V 123.047 Q 600.977,86.1328 592.48,76.4648 581.055,63.5742 558.496,63.5742 H 543.555 V 52.7344 H 715.234 Q 763.281,52.7344 792.285,59.7656 836.23,70.3125 859.375,97.2656 882.52,123.926 882.52,158.789 882.52,188.672 864.355,212.402 846.191,235.84 810.742,247.266 Z M 657.227,231.445 Q 668.066,233.496 681.836,234.668 695.898,235.547 712.598,235.547 755.371,235.547 776.758,226.465 798.437,217.09 809.863,198.047 821.289,179.004 821.289,156.445 821.289,121.582 792.871,96.9727 764.453,72.3633 709.961,72.3633 680.664,72.3633 657.227,78.8086 Z M 657.227,421.289 Q 691.211,429.199 724.316,429.199 777.344,429.199 805.176,405.469 833.008,381.445 833.008,346.289 833.008,323.145 820.41,301.758 807.812,280.371 779.395,268.066 750.977,255.762 709.082,255.762 690.918,255.762 678.027,256.348 665.137,256.934 657.227,258.398 Z"/>
</svg>
<canvas width="1000" height="500" style="position:absolute"></canvas>

答案 1 :(得分:1)

SVG滤镜是一项强大的功能,可以像浏览器中的photoshop一样。通过将少数几个过滤器原语链接在一起,可以实现所需的结果。

<filter id="filterData">
    <feGaussianBlur stdDeviation="5" />
    <feDiffuseLighting surfaceScale="500">
         <feDistantLight azimuth="90" elevation="90" />
    </feDiffuseLighting>
    <feComposite result="composite" operator="in" in2="SourceGraphic" /> 
</filter>

第一个基元使文本模糊。然后,照明图元将模糊图元的结果用作凹凸贴图,以提供文本深度。您将不得不根据文本的粗细使用surfaceScale属性。复合原语会将最终结果剪切到未经过滤的文本区域“ SourceGraphic”。

[codepen示例] https://codepen.io/lahaymd/pen/EdNXam