资讯专栏INFORMATION COLUMN

Canvas 涂鸦

thekingisalwaysluc / 464人阅读

摘要:第二步,消除涂鸦锯齿的办法简单的绘制和图片保存完成了,但是在这种情况下,线条会有很明显的锯齿灵魂画手来了。在经过搜索查阅之后,发现有一个绘制办法可以降低锯齿的问题。橡皮擦的原理是,将橡皮绘制的路径覆盖到原来的画笔上。

第一步,我们先实现简单的绘制,并且在绘制之后将图片保存到本地
    var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d"),
    iptColor = document.getElementById("iptColor"),//画笔颜色
    iptSize = document.getElementById("iptSize"),//画笔大小
    btnClear = document.getElementById("btnClear"), //清除按钮
    btnSave = document.getElementById("btnSave"),//保存按钮
    canvasWidth = 800,
    canvasHeight = 600;
    canvas.setAttribute("width",canvasWidth);
    canvas.setAttribute("height",canvasHeight);    

    iptSize.oninput = function() {
        document.querySelector(".txt-size").innerHTML = iptSize.value
    }

    canvas.addEventListener("mousedown",function(e){
        var e = e || window.event;
        ctx.lineWidth = iptSize.value; 
        ctx.strokeStyle = iptColor.value;
        ctx.lineCap = "round";
        ctx.lineJoin = "round";
        ctx.beginPath();
        ctx.moveTo(e.clientX - canvas.offsetLeft,e.clientY - canvas.offsetTop); //线条起始位置
        document.onmousemove = function(e) {
            var e = e || window.event;        
            ctx.lineTo(e.clientX - canvas.offsetLeft,e.clientY - canvas.offsetTop);
            ctx.stroke();//绘制线条
        };
        canvas.onmouseup = function() {
            document.onmousemove = null;
            document.onmouseup = null;
        };
    })
    //清除画布
    btnClear.addEventListener("click",function(){
        ctx.clearRect(0, 0, canvasWidth, canvasHeight);
    })
    
    //保存图片到本地
    btnSave.addEventListener("click",function(){
        var imgData = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
        var filename = "图片.png";
        saveFile(imgData,filename)
    })
    
    var saveFile = function(data, filename) {  
        var save_link = document.createElementNS("http://www.w3.org/1999/xhtml", "a");  
        save_link.href = data;  
        save_link.download = filename;  
        var event = document.createEvent("MouseEvents");  
        event.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);  
        save_link.dispatchEvent(event);  
    };

这样看起来虽然说好像没问题,但是这一句有一些问题:

e.clientX - canvas.offsetLeft,e.clientY - canvas.offsetTop

如果页面比较高,产生了滚动条,当滚动条向上滑的时候,绘图的位置就不准确了,示例如:https://codepen.io/jianxiujiu...。

所以此处应该使用

e.clientX - canvas.getBoundingClientRect().left,e.clientY - canvas.getBoundingClientRect().top

来替换。

第二步,消除涂鸦锯齿的办法

简单的绘制和图片保存完成了,但是在这种情况下,线条会有很明显的锯齿(灵魂画手来了)。

有一个简单粗暴的方法,将线条的边缘进行模糊:

ctx.shadowBlur = 1;
ctx.shadowColor = iptColor.value;

示例:https://codepen.io/jianxiujiu...
加上边缘模糊之后,线条明显柔和了许多,但是还是有一个问题,就是在画笔收笔的地方,线条会变小(灵魂画手又来了)。

在经过搜索查阅之后,发现有一个绘制办法可以降低锯齿的问题。
原理如图:

绘制一个圆点,在下一个圆点之间,绘制一个矩形进行填充。这样绘制出来的线条的衔接会是圆滑的。

    var canvas = document.getElementById("canvas"),
        ctx = canvas.getContext("2d"),
        btnClear = document.getElementById("btnClear"),
        btnSave = document.getElementById("btnSave"),
        iptColor = document.getElementById("iptColor"),
        iptSize = document.getElementById("iptSize"),
        canvasWidth = 800,
        canvasHeight = 600;
    canvas.setAttribute("width",canvasWidth);
    canvas.setAttribute("height",canvasHeight);    
    
    iptSize.oninput = function() {
        document.querySelector(".txt-size").innerHTML = iptSize.value
    }
    canvas.addEventListener("mousedown",function(e){
        var e = e || window.event;
        var x1 = e.clientX - canvas.getBoundingClientRect().left,
            y1 = e.clientY - canvas.getBoundingClientRect().top;
        
        lineSize = iptSize.value;
        lineColor = iptColor.value;
        ctx.beginPath();
        ctx.fillStyle = lineColor;
        ctx.lineCap = "round";
        ctx.lineJoin = "round";
        ctx.arc(x1, y1, lineSize, 0, 2 * Math.PI);
        ctx.fill();
        ctx.closePath();
        
        document.onmousemove = function(e) {
            var e = e || window.event;    
            var x2 = e.clientX - canvas.getBoundingClientRect().left,
                y2 = e.clientY - canvas.getBoundingClientRect().top;
            var asin = lineSize * Math.sin(Math.atan((y2 - y1) / (x2 - x1)));
            var acos = lineSize * Math.cos(Math.atan((y2 - y1) / (x2 - x1)));
            //分别获取矩形的四个点的xy轴位置
            var x3 = x1 + asin;
            var y3 = y1 - acos;
            var x4 = x1 - asin;
            var y4 = y1 + acos;
            var x5 = x2 + asin;
            var y5 = y2 - acos;
            var x6 = x2 - asin;
            var y6 = y2 + acos;
                                
            ctx.beginPath();
            ctx.fillStyle = lineColor;
            ctx.arc(x2, y2, lineSize, 0, 2 * Math.PI);
            ctx.fill();
            ctx.closePath();
            ctx.beginPath();
            ctx.fillStyle = lineColor;
            ctx.moveTo(x3, y3);
            ctx.lineTo(x5, y5);
            ctx.lineTo(x6, y6);
            ctx.lineTo(x4, y4);
            ctx.fill();
            ctx.closePath();
            x1 = x2;
            y1 = y2;
        };
        canvas.onmouseup = function() {
            document.onmousemove = null;
            document.onmouseup = null;
        };
    })

    btnClear.addEventListener("click",function(){
        ctx.clearRect(0, 0, canvasWidth, canvasHeight);
    })

    btnSave.addEventListener("click",function(){
        var imgData = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
        var filename = "图片.png";
        saveFile(imgData,filename)
        
    })
    
    var saveFile = function(data, filename) {  
        var save_link = document.createElementNS("http://www.w3.org/1999/xhtml", "a");  
        save_link.href = data;  
        save_link.download = filename;  
        var event = document.createEvent("MouseEvents");  
        event.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);  
        save_link.dispatchEvent(event);  
    };
         

DEMO:
https://codepen.io/jianxiujiu...

第三步,添加橡皮擦工具

用这个方法,我们还可以加上橡皮擦。
橡皮擦的原理是,将橡皮绘制的路径覆盖到原来的画笔上。
这里我们将用到canvas的一个属性globalCompositeOperation,详解参照:
http://www.w3school.com.cn/ta...

var canvas = document.getElementById("canvas"),
        ctx = canvas.getContext("2d"),
        btnClear = document.getElementById("btnClear"),
        btnSave = document.getElementById("btnSave"),
        btnRestore = document.getElementById("btnRestore"),
        iptColor = document.getElementById("iptColor"),
        iptSize = document.getElementById("iptSize"),
        eraser = document.getElementById("eraser")
        pen = document.getElementById("pen"),
        canvasWidth = 800,
        canvasHeight = 600,
        isClear = 0;
    canvas.setAttribute("width",canvasWidth);
    canvas.setAttribute("height",canvasHeight);    
    
    iptSize.oninput = function() {
        document.querySelector(".txt-size").innerHTML = iptSize.value
    }
    eraser.addEventListener("click",function(){
        isClear = 1;
        this.classList.add("cho");
        pen.classList.remove("cho")
    })
    pen.addEventListener("click",function(){ 
        isClear = 0;
        this.classList.add("cho");
        eraser.classList.remove("cho")
    })
    canvas.addEventListener("mousedown",function(e){
        var e = e || window.event;
        var x1 = e.clientX - canvas.getBoundingClientRect().left,
            y1 = e.clientY - canvas.getBoundingClientRect().top;
            lineSize = iptSize.value;
        if(isClear == 0){      
            lineColor = iptColor.value;
            ctx.beginPath();
            ctx.fillStyle = lineColor;
            ctx.arc(x1, y1, lineSize, 0, 2 * Math.PI);
            ctx.fill();
            ctx.closePath();
            document.onmousemove = function(e) {
                draw();
            };
            btnClear.classList.remove("dis");
            btnSave.classList.remove("dis");
        }else{
            ctx.strokeStyle = "rgba(250,250,250,0)";
            document.onmousemove = function() {
                ctx.globalCompositeOperation = "destination-out";    
                draw();
                ctx.globalCompositeOperation = "source-over"
            }
        }

        canvas.onmouseup = function() {
            document.onmousemove = null;
            document.onmouseup = null;
        };

        draw = function(e){
            var e = e || window.event,
                x2 = e.clientX  - canvas.getBoundingClientRect().left,
                y2 = e.clientY  - canvas.getBoundingClientRect().top,
                asin = lineSize * Math.sin(Math.atan((y2 - y1) / (x2 - x1))),
                acos = lineSize * Math.cos(Math.atan((y2 - y1) / (x2 - x1))),
                x3 = x1 + asin,
                y3 = y1 - acos,
                x4 = x1 - asin,
                y4 = y1 + acos,
                x5 = x2 + asin,
                y5 = y2 - acos,
                x6 = x2 - asin,
                y6 = y2 + acos;                          
            ctx.beginPath();
            ctx.arc(x2, y2, lineSize, 0, 2 * Math.PI);
            ctx.fill();
            ctx.closePath();
            ctx.beginPath();
            ctx.fillStyle = lineColor;
            ctx.moveTo(x3, y3);
            ctx.lineTo(x5, y5);
            ctx.lineTo(x6, y6);
            ctx.lineTo(x4, y4);
            ctx.fill();
            ctx.closePath();
            x1 = x2;
            y1 = y2;
        }
    })

    // 清除
    btnClear.addEventListener("click",function(){
        ctx.clearRect(0, 0, canvasWidth, canvasHeight);
        this.classList.add("dis");
        btnSave.classList.add("dis");
    })

    //保存图片
    btnSave.addEventListener("click",function(){
        var imgData = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
        var filename = "图片.png";
        saveFile(imgData,filename);
        this.classList.add("dis");
        
    })
    
    var saveFile = function(data, filename) {  
        var save_link = document.createElementNS("http://www.w3.org/1999/xhtml", "a");  
        save_link.href = data;  
        save_link.download = filename;  
        var event = document.createEvent("MouseEvents");  
        event.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);  
        save_link.dispatchEvent(event);  
    };

地址:https://codepen.io/jianxiujiu...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89025.html

相关文章

  • vue组件:canvas实现图片涂鸦功能

    摘要:方案背景需求需要对图片进行标注,导出图片。对应方案用实现涂鸦圆形矩形的绘制,最终生成图片编码用于上传大量图片批量上传很耗时间,为了提高用户体验,改为只实现圆形矩形绘制,最终保存成坐标,下次显示时根据坐标再绘制。 方案背景 需求 需要对图片进行标注,导出图片。 需要标注N多图片最后同时保存。 需要根据多边形区域数据(区域、颜色、名称)标注。 对应方案 用canvas实现涂鸦、圆形、...

    roland_reed 评论0 收藏0
  • 利用vue制作在线涂鸦

    摘要:撤销清空等操作撤销前进清空清空前后数据恢复到默认数据地址查看代码 效果展示 showImg(https://segmentfault.com/img/bVHJXf?w=1550&h=846); Canvas API简介 调用方法 getImageData() 返回ImageData对象,该对象为画布上指定的矩形复制像素数据 putImageData() 把图像数据(从指定的 Imag...

    nemo 评论0 收藏0
  • Android:涂鸦tuytaSDK实现逻辑

    摘要:开启扫描时需要设备处于配网状态一分类配网子设备可以通过使用手机蓝牙直接扫描获取设备到设备基础信息,再使用配网接口实现设备的本地配网。   (一)分类   (二)设备配置   (三)设备管理     设备管理,大体分为两类,mesh 和 其他     获取设备列表,给涂鸦sdk发送当前房间id...

    MorePainMoreGain 评论0 收藏0

发表评论

0条评论

thekingisalwaysluc

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<