zoukankan      html  css  js  c++  java
  • canvas小画板——(2)荧光笔效果

    我们在上一篇文章中讲了如何绘制平滑曲线 canvas小画板——(1)平滑曲线

    透明度实现荧光笔

    现在我们需要加另外一种画笔效果,带透明度的荧光笔。那可能会觉得绘制画笔的时候加上透明度就可以了。我们来在原来代码上设置

    ctx.globalAlpha属性为0.3,或者将strokeStyle设置为rgba的形式如rgba(55,55,55,0.3),代码如下:
    <!doctype html>
    <html>
    
    <head>
        <meta charset=utf-8>
        <style>
            canvas {
                border: 1px solid #ccc
            }
    
            body {
                margin: 0;
            }
        </style>
    </head>
    
    <body style="overflow: hidden;background-color: rgb(250, 250, 250);touch-action: none;">
        <canvas id="c" width="1920" height="1080"></canvas>
        <script>
            var el = document.getElementById('c');
            var ctx = el.getContext('2d');
            //设置绘制线条样式
            ctx.globalAlpha=0.3;
            ctx.strokeStyle = 'red';
            ctx.lineWidth = 10;
            ctx.lineJoin = 'round';
            ctx.lineCap = 'round';
            var isDrawing;//标记是否要绘制
            //存储坐标点
            let points = [];
            document.body.onpointerdown = function (e) {
                console.log('pointerdown');
                isDrawing = true;
                points.push({ x: e.clientX, y: e.clientY });
            };
            document.body.onpointermove = function (e) {
                console.log('pointermove');
                if (isDrawing) {
                    draw(e.clientX, e.clientY);
                }
    
            };
            document.body.onpointerup = function (e) {
                if (isDrawing) {
                    draw(e.clientX, e.clientY);
                }
                points = [];
                isDrawing = false;
            };
    
            function draw(mousex, mousey) {
                points.push({ x: mousex, y: mousey });
                ctx.beginPath();
                let x = (points[points.length - 2].x + points[points.length - 1].x) / 2,
                    y = (points[points.length - 2].y + points[points.length - 1].y) / 2;
                if (points.length == 2) {
                    ctx.moveTo(points[points.length - 2].x, points[points.length - 2].y);
                    ctx.lineTo(x, y);
                } else {
                    let lastX = (points[points.length - 3].x + points[points.length - 2].x) / 2,
                        lastY = (points[points.length - 3].y + points[points.length - 2].y) / 2;
                    ctx.moveTo(lastX, lastY);
                    ctx.quadraticCurveTo(points[points.length - 2].x, points[points.length - 2].y, x, y);
                }
                ctx.stroke();
                points.slice(0, 1);
    
            }
        </script>
    </body>
    
    </html>
    View Code

    我们鼠标画线出来的效果如下,可以看到有很多重叠区域:

    对canvas有所了解的同学,知道

    lineJoin和
    lineCap的话可能会尝试改变这两个属性,实现之后也有同样重叠的效果。

      

     

    解决荧光笔重叠问题 

    为什么会有这种重叠渲染颜色的问题呢?细细品味代码,你会发现是因为每次move的时候绘制的部分是上个鼠标点和当前鼠标点之前的连线,这样就会导致头部和尾部有重叠部分多次被stroke了。(不同连接设置的头部尾部重叠不同)

    为了避免出现上述重叠这种问题下面介绍两种方法。

    利用globalCompositeOperation

    现在我们需要用上另外一个api方法

    globalCompositeOperation,具体介绍可以看我另外一篇博文讲的比较详细(Canvas学习:globalCompositeOperation详解)。这个小画板荧光笔效果我们需要使用globalCompositeOperation=‘xor’,另外注意透明度的设置不要使用context.globalAlpha,在设置strokeStyle的时候用rgba设置透明度颜色。这个设置也是我不断尝试得出来的,具体为什么可以我也无法给出说法,有待研究或者知道的博友可以在评论给出答案。

     1 <!doctype html>
     2 <html>
     3 
     4 <head>
     5     <meta charset=utf-8>
     6     <style>
     7         canvas {
     8             border: 1px solid #ccc
     9         }
    10 
    11         body {
    12             margin: 0;
    13         }
    14     </style>
    15 </head>
    16 
    17 <body style="overflow: hidden;background-color: rgb(250, 250, 250);touch-action: none;">
    18     <canvas id="c" width="1920" height="1080"></canvas>
    19     <script>
    20         var el = document.getElementById('c');
    21         var ctx = el.getContext('2d');
    22         //设置绘制线条样式
    23         ctx.strokeStyle = 'rgba(253, 58, 43, 0.5)';
    24         ctx.lineWidth = 10;
    25         ctx.lineJoin = 'round';
    26         ctx.lineCap = 'round';
    27         
    28         var isDrawing;//标记是否要绘制
    29         //存储坐标点
    30         let points = [];
    31         document.body.onpointerdown = function (e) {
    32             console.log('pointerdown');
    33             isDrawing = true;
    34             points.push({ x: e.clientX, y: e.clientY });
    35         };
    36         document.body.onpointermove = function (e) {
    37             console.log('pointermove');
    38             if (isDrawing) {
    39                 draw(e.clientX, e.clientY);
    40             }
    41 
    42         };
    43         document.body.onpointerup = function (e) {
    44             if (isDrawing) {
    45                 draw(e.clientX, e.clientY);
    46             }
    47             points = [];
    48             isDrawing = false;
    49         };
    50 
    51         function draw(mousex, mousey) {
    52             points.push({ x: mousex, y: mousey });
    53             ctx.globalCompositeOperation = "xor";//使用异或操作对源图像与目标图像进行组合。
    54             ctx.beginPath();
    55             let x = (points[points.length - 2].x + points[points.length - 1].x) / 2,
    56                 y = (points[points.length - 2].y + points[points.length - 1].y) / 2;
    57             if (points.length == 2) {
    58                 ctx.moveTo(points[points.length - 2].x, points[points.length - 2].y);
    59                 ctx.lineTo(x, y);
    60             } else {
    61                 let lastX = (points[points.length - 3].x + points[points.length - 2].x) / 2,
    62                     lastY = (points[points.length - 3].y + points[points.length - 2].y) / 2;
    63                 ctx.moveTo(lastX, lastY);
    64                 ctx.quadraticCurveTo(points[points.length - 2].x, points[points.length - 2].y, x, y);
    65             }
    66             ctx.stroke();
    67             points.slice(0, 1);
    68 
    69         }
    70     </script>
    71 </body>
    72 
    73 </html>

    存储坐标点

    另有一种普遍做法是使用数组points存储每个点的坐标值,每次绘制前先清除画布内容,再循环points数组绘制路径,最后进行一次stroke。

    这种方法每次只能保留一条线条,因为在不断的清除画布内容,如果需要保留住的话,可以扩展下points为二维数组,保留每一条线条的所有鼠标点。清除画布后遍历points数组重绘所有线条。

     1 <!doctype html>
     2 <html>
     3 
     4 <head>
     5     <meta charset=utf-8>
     6     <style>
     7         canvas {
     8             border: 1px solid #ccc
     9         }
    10 
    11         body {
    12             margin: 0;
    13         }
    14     </style>
    15 </head>
    16 
    17 <body style="overflow: hidden;background-color: rgb(250, 250, 250);touch-action: none;">
    18     <canvas id="c" width="1920" height="1080"></canvas>
    19     <script>
    20         var el = document.getElementById('c');
    21         var ctx = el.getContext('2d');
    22         //设置绘制线条样式
    23         ctx.globalAlpha = 0.3;
    24         ctx.strokeStyle = 'red';
    25         ctx.lineWidth = 10;
    26         ctx.lineJoin = 'round';
    27         ctx.lineCap = 'round';
    28         var isDrawing;//标记是否要绘制
    29         //存储坐标点
    30         let points = [];
    31         document.body.onpointerdown = function (e) {
    32             console.log('pointerdown');
    33             isDrawing = true;
    34             points.push({ x: e.clientX, y: e.clientY });
    35         };
    36         document.body.onpointermove = function (e) {
    37             console.log('pointermove');
    38             if (isDrawing) {
    39                 points.push({ x: e.clientX, y: e.clientY });
    40                 draw(e.clientX, e.clientY);
    41             }
    42 
    43         };
    44         document.body.onpointerup = function (e) {
    45             if (isDrawing) {
    46                 points.push({ x: e.clientX, y: e.clientY });
    47                 draw(e.clientX, e.clientY);
    48             }
    49             points = [];
    50             isDrawing = false;
    51         };
    52 
    53         function draw(mousex, mousey) {
    54             ctx.clearRect(0, 0, 1920, 1080);
    55             ctx.beginPath();
    56             for (let i = 0; i < points.length; i++) {
    57                 if (i == 0)
    58                     ctx.moveTo(points[i].x, points[i].y);
    59                 else {
    60                     let p0 = points[i];
    61                     let p1 = points[i + 1];
    62                     let c, d;
    63                     if (!p1) {
    64                         c = p0.x;
    65                         d = p0.y;
    66                     }else {
    67                         c = (p0.x + p1.x) / 2;
    68                         d = (p0.y + p1.y) / 2;
    69                     }
    70                     ctx.quadraticCurveTo(p0.x, p0.y, c, d); //二次贝塞曲线函数   
    71                 }
    72             }
    73             ctx.stroke();
    74         }
    75     </script>
    76 </body>
    77 
    78 </html>

    两种解决方法对比 

    这两种方法都可以实现荧光笔的效果,如下截图:

     第一种方法只绘制上个点和当前点,而第二种需要绘制所有线条,所以从流畅性上对比第一种有优势。但如果需要实现橡皮擦的功能第一种就满足不了了,我的一篇博文中具体介绍了橡皮擦的实现可以参看

    清除canvas画布内容--点擦除+线擦除

  • 相关阅读:
    cmake Found package configuration file but it set OpenCV_FOUND to FALSE
    pthread库"timespec"结构体重定义解决
    【Windows10】运行软件后,窗口不显示的解决办法
    Windows下Cmake生成动态库install命令失败、导入库概念
    【转载】多尺度增强算法Retinex算法(MSRCR)的原理、实现及应用
    【转载】Ubuntu 和 Windows 之间进行远程访问和文件互传
    博客园Markdown编辑器
    合并两个有序数组(C++)
    从协方差矩阵的估算领会MATLAB矩阵编程思维
    常见排序算法的性能对比
  • 原文地址:https://www.cnblogs.com/fangsmile/p/13441493.html
Copyright © 2011-2022 走看看