SVG smooth freehand drawing

耗尽温柔 提交于 2020-12-27 08:15:07

问题


I implemented a freehand drawing of a path using native JS. But as expected path edges are little aggressive and not smooth. So I have an option of using simplifyJS to simplify points and then redraw path. But like here, instead of smoothening after drawing, I am trying to find simplified edges while drawing

Here is my code:

    var x0, y0;

    var dragstart = function(event) {
        var that = this;
        var pos = coordinates(event);
        x0 = pos.x;
        y0 = pos.y;
        that.points = [];
    };

    var dragging = function(event) {
        var that = this;
        var xy = coordinates(event);
        var points = that.points;
        var x1 = xy.x, y1 = xy.y, dx = x1 - x0, dy = y1 - y0;
        if (dx * dx + dy * dy > 100) {
            xy = {
                x: x0 = x1, 
                y: y0 = y1
            };
        } else {
            xy = {
                x: x1, 
                y: y1
            };
        }
        points.push(xy);
    };

But it is not working as in the link added above. Still edges are not good. Please help.


回答1:


The following code snippet makes the curve smoother by calculating the average of the last mouse positions. The level of smoothing depends on the size of the buffer in which these values are kept. You can experiment with the different buffer sizes offered in the dropdown list. The behavior with a 12 point buffer is somewhat similar to the Mike Bostock's code snippet that you refer to in the question.

More sophisticated techniques could be implemented to get the smoothed point from the positions stored in the buffer (weighted average, linear regression, cubic spline smoothing, etc.) but this simple average method may be sufficiently accurate for your needs.

var strokeWidth = 2;
var bufferSize;

var svgElement = document.getElementById("svgElement");
var rect = svgElement.getBoundingClientRect();
var path = null;
var strPath;
var buffer = []; // Contains the last positions of the mouse cursor

svgElement.addEventListener("mousedown", function (e) {
    bufferSize = document.getElementById("cmbBufferSize").value;
    path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute("fill", "none");
    path.setAttribute("stroke", "#000");
    path.setAttribute("stroke-width", strokeWidth);
    buffer = [];
    var pt = getMousePosition(e);
    appendToBuffer(pt);
    strPath = "M" + pt.x + " " + pt.y;
    path.setAttribute("d", strPath);
    svgElement.appendChild(path);
});

svgElement.addEventListener("mousemove", function (e) {
    if (path) {
        appendToBuffer(getMousePosition(e));
        updateSvgPath();
    }
});

svgElement.addEventListener("mouseup", function () {
    if (path) {
        path = null;
    }
});

var getMousePosition = function (e) {
    return {
        x: e.pageX - rect.left,
        y: e.pageY - rect.top
    }
};

var appendToBuffer = function (pt) {
    buffer.push(pt);
    while (buffer.length > bufferSize) {
        buffer.shift();
    }
};

// Calculate the average point, starting at offset in the buffer
var getAveragePoint = function (offset) {
    var len = buffer.length;
    if (len % 2 === 1 || len >= bufferSize) {
        var totalX = 0;
        var totalY = 0;
        var pt, i;
        var count = 0;
        for (i = offset; i < len; i++) {
            count++;
            pt = buffer[i];
            totalX += pt.x;
            totalY += pt.y;
        }
        return {
            x: totalX / count,
            y: totalY / count
        }
    }
    return null;
};

var updateSvgPath = function () {
    var pt = getAveragePoint(0);

    if (pt) {
        // Get the smoothed part of the path that will not change
        strPath += " L" + pt.x + " " + pt.y;

        // Get the last part of the path (close to the current mouse position)
        // This part will change if the mouse moves again
        var tmpPath = "";
        for (var offset = 2; offset < buffer.length; offset += 2) {
            pt = getAveragePoint(offset);
            tmpPath += " L" + pt.x + " " + pt.y;
        }

        // Set the complete current path coordinates
        path.setAttribute("d", strPath + tmpPath);
    }
};
html, body
{
    padding: 0px;
    margin: 0px;
}
#svgElement
{
    border: 1px solid;
    margin-top: 4px;
    margin-left: 4px;
    cursor: default;
}
#divSmoothingFactor
{
    position: absolute;
    left: 14px;
    top: 12px;
}
<div id="divSmoothingFactor">
    <label for="cmbBufferSize">Buffer size:</label>
    <select id="cmbBufferSize">
        <option value="1">1 - No smoothing</option>
        <option value="4">4 - Sharp curves</option>
        <option value="8" selected="selected">8 - Smooth curves</option>
        <option value="12">12 - Very smooth curves</option>
        <option value="16">16 - Super smooth curves</option>
        <option value="20">20 - Hyper smooth curves</option>
    </select>
</div>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="svgElement" x="0px" y="0px" width="600px" height="400px" viewBox="0 0 600 400" enable-background="new 0 0 600 400" xml:space="preserve">



回答2:


There are already some implementations for this on github e.g. https://github.com/epistemex/cardinal-spline-js

You dont have to change anything on your input for that and can only change the draw function, that the line between the points is smooth. With that the points dont slip a bit during the simplification.



来源:https://stackoverflow.com/questions/40324313/svg-smooth-freehand-drawing

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!