javascript - 在 HTML5 Canvas 中实现画笔厚度变化(示例想要在内部模拟)

标签 javascript html canvas drawing paint

我希望在下面的演示中指出算法方面的正确方向 http://sta.sh/muro/。还有它正在使用的 Canvas 工具 - 即它是绘制线条还是绘制许多弧线等

具体来说,我想模拟画笔转动,这会导致整个“画笔笔触”更粗。请参阅图像以了解我要模拟的画笔设置。

最终,我希望创建一个在转动时厚度会变化的画笔,就像下面的行为一样。

enter image description here

最佳答案

为此,您需要记录按下按钮时的鼠标点。然后,您需要检查每个线段以找到方向、线的长度以及该线段的归一化向量,以便您可以对鼠标样本进行过采样。

因此,如果您有一组从鼠标获取的点,则以下内容将获得所需的详细信息。

    for(var i = 0; i < len-1; i++){
        var p1 = line[i];
        var p2 = line[i+1];
        var nx = p2.x - p1.x;
        var ny = p2.y - p1.y;
        p1.dir = ((Math.atan2(ny,nx)%PI2)+PI2)%PI2; // get direction
        p1.len = Math.hypot(nx,ny);  // get length
        p1.nx = nx/p1.len;  // get normalised vector
        p1.ny = ny/p1.len;
    }

一旦掌握了每条线段的详细信息,根据值更改绘图参数就很简单了。

我添加了一个演示。它与我可能会费心去了解示例一样接近,因为您提供的行没有提供绘制图像所显示内容的选项。该图像显示他们也使用阴影并进行子小鼠样本采样。他们绘制的圆形框可能是一个图像,比我绘制的框要快得多。我还对线条进行了一些平滑处理,因此绘图与最终确定并设置为背景图像之间存在一点滞后。

演示的顶部是一组控制各种设置的常量。添加输入选项和审查会花费太多时间,因此如果您发现代码有用,请使用它们。

抱歉,代码很困惑,但这只是一个示例,您必须自己将其拆开。

/** hypot.js begin **/
// ES6 new math function hypot. Sqrt of the sum of the squares
var hypot = Math.hypot;
if(typeof hypot === 'undefined'){
    hypot = function(x,y){
        return Math.sqrt(Math.pow(x,2)+Math.pow(y,2));
    }
}

/** hypot.js end **/

// draw options
const SUB_SECTIONS = 5; // points between mouse samples
const SIZE_MULT = 3; // Max size multiplier
const SIZE_MIN = 0.1 // min size of line
const BIG_DIR = 0.6;  // direction in radians for thickest line
const SMOOTH_MAX = 7;  // number of smoothing steps performed on a line. Bigger that 20 will slow the rendering down
const SHAPE_ALPHA = 0.5;  // the stoke alpha
const SHAPE_FILL_ALPHA = 0.75; // the fill alpha
const SHADOW_ALPHA = 0.1;   // the shadow alpha
const SHADOW_BLUR = 5;  // the shadow blur
const SHADOW_OFFX = 6;  // shoadow offest x and y
const SHADOW_OFFY = 6;
const SHAPE_LINE_WIDTH = 0.6;  // stroke width of shape. This is constant and is not scaled
const SHAPE_WIDTH = 4;  // shape drawn width;
const SHAPE_LENGTH = 20;  // shape drawn length
const SHAPE_ROUNDING = 2;  // shape rounded corner radius. Warning invalid results if rounding is greater than half width or height which ever is the smallest
const SHAPE_TRAIL = 0;  // offset  draw shape. Negivive numbers trail drawing positive are infront

var div = document.createElement("div"); 
div.textContent = "Click drag mouse to draw, Right click to clear."
document.body.appendChild(div);

var mouse;
var demo = function(){
    
    /** fullScreenCanvas.js begin **/
    var canvas = (function(){
        var canvas = document.getElementById("canv");
        if(canvas !== null){
            document.body.removeChild(canvas);
        }
        // creates a blank image with 2d context
        canvas = document.createElement("canvas"); 
        canvas.id = "canv";    
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight; 
        canvas.style.position = "absolute";
        canvas.style.top = "0px";
        canvas.style.left = "0px";
        canvas.style.zIndex = 1000;
        canvas.ctx = canvas.getContext("2d"); 
        document.body.appendChild(canvas);
        return canvas;
    })();
    var ctx = canvas.ctx;
    
    /** fullScreenCanvas.js end **/
    /** MouseFull.js begin **/
    if(typeof mouse !== "undefined"){  // if the mouse exists 
        if( mouse.removeMouse !== undefined){
            mouse.removeMouse(); // remove previouse events
        }
    }
    var canvasMouseCallBack = undefined;  // if needed
    mouse = (function(){
        var mouse = {
            x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false,
            interfaceId : 0, buttonLastRaw : 0,  buttonRaw : 0,
            over : false,  // mouse is over the element
            bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
            getInterfaceId : function () { return this.interfaceId++; }, // For UI functions
            startMouse:undefined,
            mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
        };
        function mouseMove(e) {
            var t = e.type, m = mouse;
            m.x = e.offsetX; m.y = e.offsetY;
            if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
            m.alt = e.altKey;m.shift = e.shiftKey;m.ctrl = e.ctrlKey;
            if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
            } else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
            } else if (t === "mouseout") { m.buttonRaw = 0; m.over = false;
            } else if (t === "mouseover") { m.over = true;
            } else if (t === "mousewheel") { m.w = e.wheelDelta;
            } else if (t === "DOMMouseScroll") { m.w = -e.detail;}
            if (canvasMouseCallBack) { canvasMouseCallBack(mouse); }
            e.preventDefault();
        }
        function startMouse(element){
            if(element === undefined){
                element = document;
            }
            mouse.element = element;
            mouse.mouseEvents.forEach(
                function(n){
                    element.addEventListener(n, mouseMove);
                }
            );
            element.addEventListener("contextmenu", function (e) {e.preventDefault();}, false);
        }
        mouse.removeMouse = function(){
            if(mouse.element !== undefined){
                mouse.mouseEvents.forEach(
                    function(n){
                        mouse.element.removeEventListener(n, mouseMove);
                    }
                );
                canvasMouseCallBack = undefined;
            }
        }
        mouse.mouseStart = startMouse;
        return mouse;
    })();
    if(typeof canvas !== "undefined"){
        mouse.mouseStart(canvas);
    }else{
        mouse.mouseStart();
    }
    /** MouseFull.js end **/
    /** CreateImage.js begin **/
    // creates a blank image with 2d context
    var createImage=function(w,h){var i=document.createElement("canvas");i.width=w;i.height=h;i.ctx=i.getContext("2d");return i;}
    
    /** CreateImage.js end **/
    /** FrameUpdate.js begin **/
    var w = canvas.width;
    var h = canvas.height;
    var cw = w / 2;
    var ch = h / 2;
    var line = []; // line to hold drawing points
    var image = createImage(w,h); // Background image to dump point to when soothed
    
    var PI2 = Math.PI * 2; // 360 to save typing 
    var PIh = Math.PI / 2; // 90 
    
    // draws a rounded rectangle path
    function roundedRect(ctx,x, y, w, h, r){

        ctx.beginPath(); 
        ctx.arc(x + r, y + r, r, PIh * 2, PIh * 3);  
        ctx.arc(x + w - r, y + r, r, PIh * 3, PI2);
        ctx.arc(x + w - r, y + h - r, r, 0, PIh);  
        ctx.arc(x + r, y + h - r, r, PIh, PIh * 2);  
        ctx.closePath(); 
    }

    // this draws a section of line
    function drawStroke(ctx,line){
        var len = line.length;

        ctx.shadowBlur = SHADOW_BLUR;
        ctx.shadowOffsetX = SHADOW_OFFX;
        ctx.shadowOffsetY = SHADOW_OFFY;
        ctx.shadowColor = "rgba(0,0,0," + SHADOW_ALPHA + ")";
        ctx.strokeStyle = "rgba(0,0,0," + SHAPE_FILL_ALPHA + ")";
        ctx.fillStyle = "rgba(255,255,255," + SHAPE_ALPHA + ")";
        for (var i = 0; i < len - 1; i++) { // for each point minus 1
            var p1 = line[i];
            var p2 = line[i + 1]; // get the point and one ahead
            if (p1.dir && p2.dir) { // do both points have a direction
                // divide the distance between the points by 5 and draw each sub section
                for (var k = 0; k < p1.len; k += p1.len / SUB_SECTIONS) {
                    // get the points between mouse samples
                    var x = p1.x + p1.nx * k;
                    var y = p1.y + p1.ny * k;
                    var kk = k / p1.len; // get normalised distance
                    // tween direction but need to check cyclic
                    if (p1.dir > Math.PI * 1.5 && p2.dir < Math.PI / 2) {
                        var dir = ((p2.dir + Math.PI * 2) - p1.dir) * kk + p1.dir;
                    } else
                    if (p2.dir > Math.PI * 1.5 && p1.dir < Math.PI / 2) {
                        var dir = ((p2.dir - Math.PI * 2) - p1.dir) * kk + p1.dir;
                    } else {
                        var dir = (p2.dir - p1.dir) * kk + p1.dir;
                    }

                    // get size dependent on direction
                    var size = (Math.abs(Math.sin(dir + BIG_DIR)) + SIZE_MIN) * SIZE_MULT;
                    // caculate the transform requiered.
                    var xdx = Math.cos(dir) * size;
                    var xdy = Math.sin(dir) * size;
                    // set the line width to the invers scale so it remains constant
                    ctx.lineWidth = SHAPE_LINE_WIDTH * (1 / size); // make sure that the line width does not scale
                    // set the transform
                    ctx.setTransform(xdx, xdy, -xdy, xdx, x, y);
                    // draw the shape
                    roundedRect(ctx, -SHAPE_LENGTH / 2 - SHAPE_TRAIL, -SHAPE_WIDTH / 2, SHAPE_LENGTH, SHAPE_WIDTH, SHAPE_ROUNDING);
                    // fill and stroke
                    ctx.fill();
                    ctx.stroke();
                }
            }
        }
        // restore transform
        ctx.setTransform(1, 0, 0, 1, 0, 0);
    }

    // update function will try 60fps but setting will slow this down.    
    function update(){
        // restore transform
        ctx.setTransform(1, 0, 0, 1, 0, 0);
        // clear
        ctx.clearRect(0, 0, w, h);
        // get line length
        var len = line.length;
        
        if (mouse.buttonRaw !== 1) { // button up so draw all onto image
            drawStroke(image.ctx, line)
            line = [];
        } else {
            // remove trailing line segments that are no longer being smoothed
            if (len > SMOOTH_MAX * 2) {
                var a = line.splice(0, SMOOTH_MAX - 1)
                    a.push(line[0]);
                drawStroke(image.ctx, a)
            }
        }
        // draw background image
        ctx.drawImage(image, 0, 0);

        // is the button down
        if (mouse.buttonRaw === 1) {
            // if more than one point
            if (line.length > 0) {
                // only add a point if mouse has moved.
                if (mouse.x !== line[line.length - 1].x || mouse.y !== line[line.length - 1].y) {
                    line.push({
                        x : mouse.x,
                        y : mouse.y,
                        s : 0
                    });
                }
            } else {
                // add a point if no points exist
                line.push({
                    x : mouse.x,
                    y : mouse.y,
                    s : 0
                });
            }
        }
        // get number of points
        var len = line.length; 
        
        
        if(mouse.buttonRaw === 1){  // mouse down the do simple running average smooth
            // This smooth will continue to refine points untill the it is outside the
            // smoothing range/
            for (var i = 0; i < len - 3; i++) {
                var p1 = line[i];
                var p2 = line[i + 1];
                var p3 = line[i + 2];
                if (p1.s < SMOOTH_MAX) {
                    p1.s += 1;
                    p2.x = ((p1.x + p3.x) / 2 + p2.x * 2) / 3;
                    p2.y = ((p1.y + p3.y) / 2 + p2.y * 2) / 3;
                }
            }
            // caculate the direction, length and normalised vector for
            // each line segment and add to the point
            for(var i = 0; i < len-1; i++){
                var p1 = line[i];
                var p2 = line[i + 1];
                var nx = p2.x - p1.x;
                var ny = p2.y - p1.y;
                p1.dir = ((Math.atan2(ny, nx) % PI2) + PI2) % PI2; // get direction
                p1.len = hypot(nx, ny); // get length
                p1.nx = nx / p1.len; // get normalised vector
                p1.ny = ny / p1.len;
    
            }
            // draw the line points onto the canvas.
            drawStroke(ctx,line)
        }
        if((mouse.buttonRaw & 4)=== 4){
            line = [];
            image.ctx.clearRect(0,0,w,h);
            ctx.clearRect(0,0,w,h);
            mouse.buttonRaw = 0;
        }
        if(!STOP){
            requestAnimationFrame(update);
        }else{
            var can = document.getElementById("canv");
            if(can !== null){
                document.body.removeChild(can);
            }     
            STOP = false;   
            
        }
    }

    update();

}
var STOP = false;  // flag to tell demo app to stop 
function resizeEvent(){
    var waitForStopped = function(){
        if(!STOP){  // wait for stop to return to false
            demo();
            return;
        }
        setTimeout(waitForStopped,200);
    }
    STOP = true;
    setTimeout(waitForStopped,100);
}
window.addEventListener("resize",resizeEvent);
demo();
/** FrameUpdate.js end **/

关于javascript - 在 HTML5 Canvas 中实现画笔厚度变化(示例想要在内部模拟),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34823751/

相关文章:

javascript - kinetic js,我怎样才能提高元素的分辨率

javascript - 如何以有效形式仅输入 3 位数字?

javascript - 如何获取URL中 '#'之后的字符串

javascript - 如何将 Woocommerce 与 Piwik 集成

javascript - 在一定宽度时将整个网站缩放0.8

html - CSS:防止带有文本内容的子元素增大窗口大小

javascript - 在 Javascript 中保存切换元素和本地存储

javascript - getImageData - Web worker - 如何减少垃圾回收?

javascript - 带数字实心圆的 Canvas

javascript - 在粘贴事件 Javascript 中删除输入字段中的所有非数字字符