js+html5 canvas实现ps钢笔抠图


Posted in Javascript onApril 28, 2019

html5 canvas+js实现ps钢笔抠图

1. 项目要求需要用js实现photoshop中钢笔抠图功能,就用了近三四天的时间去解决它,最终还是基本上把他实现了。

做的过程中走了不少弯路,最终一同事找到了canvans以比较核心的属性globalCompositeOperation = "destination-out",

属性可以实现通过由多个点构成的闭合区间设置成透明色穿透画布背景色或是背景图片,这样省了许多事。

2.实现效果:

鼠标点完之后会将所有的点连成闭合区间,并可自由拖拉任一点,当形成闭合区间后,可在任意两点之间添加新点进行拖拉。

js+html5 canvas实现ps钢笔抠图

3.实现思路:

设置两层div,底层设置图片,顶层设置canvas画布(如果将图片渲染到画布上,抠图时会闪烁,所以至于底层),在画布上监视

  鼠标事件反复渲染点及之间连线,形成闭合区间后将整体画布渲染小块背景图片,并将闭合区间渲染透明色。并把点的相对画布

坐标记录或更新到数组中去。截完图后,将点的坐标集合传回后台,由后台代码实现根据坐标点及图片宽度高度实现截图,并设

至背景色为透明色(canvas也可以实现截图,但需要处理像素点实现背景透明,暂时还没实现,计划用C#后台代码实现)。

4.js(写的不规范比较乱,大家就当参考吧)

<script type="text/javascript">
  $(function () {
   var a = new tailorImg();
   a.iniData();
  });
  //
  var tailorImg=function()
  {
   this.iniData = function () {
    //画布
    this.can.id = "canvas";
    this.can.w = 400;
    this.can.h = 400;
    this.can.roundr = 7;
    this.can.roundrr = 3;
    this.can.curPointIndex = 0;
    this.can.imgBack.src = "gzf.png";
    this.can.canvas = document.getElementById(this.can.id).getContext("2d");
    //图片
    this.img.w = 400;
    this.img.h = 400;
    this.img.image.src = "flower.jpg";
    //加载事件:
    //初始化事件:
    var a = this;
    var p = a.can.pointList;
    $("#" + a.can.id).mousemove(function (e) {
     if (a.can.paint) {//是不是按下了鼠标 
      if (p.length > 0) {
       a.equalStartPoint(p[p.length - 1].pointx, p[p.length - 1].pointy);
      }
      a.roundIn(e.offsetX, e.offsetY);
     }
     //判断是否在直线上
     //光标移动到线的附近如果是闭合的需要重新划线,并画上新添加的点
     a.AddNewNode(e.offsetX, e.offsetY);
    });
    $("#" + a.can.id).mousedown(function (e) {
     a.can.paint = true;
     //点击判断是否需要在线上插入新的节点:
     if (a.can.tempPointList.length > 0) {
      a.can.pointList.splice(a.can.tempPointList[1].pointx, 0, new a.point(a.can.tempPointList[0].pointx, a.can.tempPointList[0].pointy));
      //清空临时数组
      a.can.tempPointList.length = 0;
     }
    });
    $("#" + a.can.id).mouseup(function (e) {
     //拖动结束
     a.can.paint = false;
     //拖动结束;
     if (a.can.juPull) {
      a.can.juPull = false;
      a.can.curPointIndex = 0;
      //验证抠图是否闭合:闭合,让结束点=开始点;添加标记
      a.equalStartPoint(p[p.length - 1].pointx, p[p.length - 1].pointy);
      //判断是否闭合:
      if (a.can.IsClose) {

      }
     }
     else {
      //如果闭合:禁止添加新的点;
      if (!a.can.IsClose) {//没有闭合
       p.push(new a.point(e.offsetX, e.offsetY));
       //验证抠图是否闭合:闭合,让结束点=开始点;添加标记
       a.equalStartPoint(p[p.length - 1].pointx, p[p.length - 1].pointy);
       //判断是否闭合:
       //重新画;
       if (p.length > 1) {
        a.drawLine(p[p.length - 2].pointx, p[p.length - 2].pointy, p[p.length - 1].pointx, p[p.length - 1].pointy);
        a.drawArc(p[p.length - 1].pointx, p[p.length - 1].pointy);
       } else {
        a.drawArc(p[p.length - 1].pointx, p[p.length - 1].pointy);
       }
      }
      else {
       //闭合
      }
     }
     //验证是否填充背景:
     if (a.can.IsClose) {
      a.fillBackColor();
      a.drawAllLine();
     }
    });
    $("#" + a.can.id).mouseleave(function (e) {
     a.can.paint = false;
    });
    //鼠标点击事件:
    $("#" + a.can.id).click(function (e) {
     //空
    });
   }
   this.point = function (x, y) {
    this.pointx = x;
    this.pointy = y;
   };
   //图片
   this.img = {
    image:new Image(),
    id: "",
    w:0,
    h:0
   };
   //画布;
   this.can = {
    canvas:new Object(),
    id: "",
    w: 0,
    h: 0,
    //坐标点集合
    pointList: new Array(),
    //临时存储坐标点
    tempPointList: new Array(),
    //圆点的触发半径:
    roundr: 7,
    //圆点的显示半径:
    roundrr: 7,
    //当前拖动点的索引值;
    curPointIndex : 0,
    //判断是否点击拖动
    paint : false,
    //判断是否点圆点拖动,并瞬间离开,是否拖动点;
    juPull : false,
    //判断是否闭合
    IsClose: false,
    imgBack: new Image()
    
   };
   //函数:
   //更新画线
   this.drawAllLine=function () {
    for (var i = 0; i < this.can.pointList.length - 1; i++) {
     //画线
     var p = this.can.pointList;
     this.drawLine(p[i].pointx, p[i].pointy, p[i + 1].pointx, p[i + 1].pointy);
     //画圈
     this.drawArc(p[i].pointx, p[i].pointy);
     if (i == this.can.pointList.length - 2) {
      this.drawArc(p[i+1].pointx, p[i+1].pointy);
     }
    }
   }
   //画线
   this.drawLine = function (startX, startY, endX, endY) {
    //var grd = this.can.canvas.createLinearGradient(0, 0,2,0); //坐标,长宽
    //grd.addColorStop(0, "black"); //起点颜色
    //grd.addColorStop(1, "white");
    //this.can.canvas.strokeStyle = grd;
    this.can.canvas.strokeStyle = "blue"
    this.can.canvas.lineWidth =1;
    this.can.canvas.moveTo(startX, startY);
    this.can.canvas.lineTo(endX, endY);
    this.can.canvas.stroke();
   }
   //画圈:
   this.drawArc=function(x, y) {
    this.can.canvas.fillStyle = "blue";
    this.can.canvas.beginPath();
    this.can.canvas.arc(x, y,this.can.roundrr, 360, Math.PI * 2, true);
    this.can.canvas.closePath();
    this.can.canvas.fill();
   }
   //光标移到线上画大圈:
   this.drawArcBig = function (x, y) {
    this.can.canvas.fillStyle = "blue";
    this.can.canvas.beginPath();
    this.can.canvas.arc(x, y, this.can.roundr+2, 360, Math.PI * 2, true);
    this.can.canvas.closePath();
    this.can.canvas.fill();
   }
   //渲染图片往画布上
   this.showImg=function() {
    this.img.image.onload = function () {
     this.can.canvas.drawImage(this.img.image, 0, 0, this.img.w,this.img.h);
    };
   }
   //填充背景色
   this.fillBackColor = function () {
    for (var i = 0; i <this.img.w; i += 96) {
     for (var j = 0; j <= this.img.h; j += 96) {
      this.can.canvas.drawImage(this.can.imgBack, i, j, 96, 96);
     }
    }
    this.can.canvas.globalCompositeOperation = "destination-out";
    this.can.canvas.beginPath();
    for (var i = 0; i <this.can.pointList.length; i++) {
     this.can.canvas.lineTo(this.can.pointList[i].pointx,this.can.pointList[i].pointy);
    }
    this.can.canvas.closePath();
    this.can.canvas.fill();
    this.can.canvas.globalCompositeOperation = "destination-over";
    this.drawAllLine();
   }
   //去掉pointlist最后一个坐标点:
   this.clearLastPoint=function () {
    this.can.pointList.pop();
    //重画:
    this.clearCan();
    this.drawAllLine();
   }
   //判断结束点是否与起始点重合;
   this.equalStartPoint = function (x,y) {
    var p = this.can.pointList;
    if (p.length > 1 && Math.abs((x - p[0].pointx) * (x - p[0].pointx)) + Math.abs((y - p[0].pointy) * (y - p[0].pointy)) <= this.can.roundr * this.can.roundr) {
     //如果闭合
     this.can.IsClose = true;
     p[p.length - 1].pointx = p[0].pointx;
     p[p.length - 1].pointy = p[0].pointy;
    }
    else {
     this.can.IsClose = false;
    }
   }
   //清空画布
   this.clearCan=function (){
    this.can.canvas.clearRect(0, 0, this.can.w, this.can.h);
   }
   //剪切区域
   this.CreateClipArea=function () {
    this.showImg();
    this.can.canvas.beginPath();
    for (var i = 0; i <this.can.pointList.length; i++) {
     this.can.canvas.lineTo(this.can.pointList[i].pointx,this.can.pointList[i].pointy);
    }
    this.can.canvas.closePath();
    this.can.canvas.clip();
   }
   //
   this.CreateClipImg=function()
   {

   }
   //判断鼠标点是不是在圆的内部:
   this.roundIn = function (x, y) {
    //刚开始拖动
    var p = this.can.pointList;
    if (!this.can.juPull) {
     for (var i = 0; i < p.length; i++) {

      if (Math.abs((x - p[i].pointx) * (x - p[i].pointx)) + Math.abs((y - p[i].pointy) * (y - p[i].pointy)) <= this.can.roundr * this.can.roundr) {
       //说明点击圆点拖动了;
       this.can.juPull = true;//拖动
       //
       this.can.curPointIndex = i;
       p[i].pointx = x;
       p[i].pointy = y;
       //重画:
       this.clearCan();
       //showImg();
       if (this.can.IsClose) {
        this.fillBackColor();
       }
       this.drawAllLine();
       return;
      }
     }
    }
    else {//拖动中
     p[this.can.curPointIndex].pointx = x;
     p[this.can.curPointIndex].pointy = y;
     //重画:
     this.clearCan();
     if (this.can.IsClose) {
      this.fillBackColor();
     }
     this.drawAllLine();
    }
   };

   //光标移到线上,临时数组添加新的节点:
   this.AddNewNode=function(newx, newy) {
    //如果闭合
    var ii=0;
    if (this.can.IsClose) {
     //判断光标点是否在线上:
     var p = this.can.pointList;
     for (var i = 0; i < p.length - 1; i++) {
      //计算a点和b点的斜率
      var k = (p[i + 1].pointy - p[i].pointy) / (p[i + 1].pointx - p[i].pointx);
      var b = p[i].pointy - k * p[i].pointx;
      //if (parseInt((p[i + 1].pointy - p[i].pointy) / (p[i + 1].pointx - p[i].pointx)) ==parseInt((p[i + 1].pointy - newy) / (p[i + 1].pointx - newx)) && newx*2-p[i+1].pointx-p[i].pointx<0 && newy*2-p[i+1].pointy-p[i].pointy<0) {
      // //如果在直线上
      // alert("在直线上");
      //}
      $("#txtone").val(parseInt(k * newx + b));
      $("#txttwo").val(parseInt(newy));
      if (parseInt(k * newx + b) == parseInt(newy) && (newx - p[i + 1].pointx) * (newx - p[i].pointx) <= 2 && (newy - p[i + 1].pointy) * (newy - p[i].pointy) <= 2) {
       //
       //parseInt(k * newx + b) == parseInt(newy)
       //添加临时点:
       this.can.tempPointList[0] = new this.point(newx, newy);//新的坐标点
       this.can.tempPointList[1] = new this.point(i+1, i+1);//需要往pointlist中插入新点的索引;
       i++;
       //alert();
       //光标移动到线的附近如果是闭合的需要重新划线,并画上新添加的点;
       if (this.can.tempPointList.length > 0) {
        //重画:
        this.clearCan();
        //showImg();
        if (this.can.IsClose) {
         this.fillBackColor();
        }
        this.drawAllLine();
        this.drawArcBig(this.can.tempPointList[0].pointx, this.can.tempPointList[0].pointy);
        return;
       }
       return;
      }
      else {
       // $("#Text1").val("");
      }
     }
     if (ii == 0) {
      if (this.can.tempPointList.length > 0) {
       //清空临时数组;
       this.can.tempPointList.length = 0;
       //重画:
       this.clearCan();
       //showImg();
       if (this.can.IsClose) {
        this.fillBackColor();
       }
       this.drawAllLine();
       //this.drawArc(this.can.tempPointList[0].pointx, this.can.tempPointList[0].pointy);
      }
     }
    }
    else {
     //防止计算误差引起的添加点,当闭合后,瞬间移动起始点,可能会插入一个点到临时数组,当再次执行时,
     //就会在非闭合情况下插入该点,所以,时刻监视:
     if (this.can.tempPointList.length > 0) {
      this.can.tempPointList.length = 0;
     }
    }
   }
   
  };

 </script>
<style type="text/css">
  .canvasDiv {
   position: relative;
   border: 1px solid red;
   height: 400px;
   width: 400px;
   top: 50px;
   left: 100px;
   z-index: 0;
  }

  img {
   width: 400px;
   height: 400px;
   z-index: 1;
   position: absolute;
  }

  #canvas {
   position: absolute;
   border: 1px solid green;
   z-index: 2;
  }
  .btnCollection {
   margin-left: 100px;
  }
 </style>
<div class="canvasDiv">
    <img src="flower.jpg" />
    <canvas id="canvas" width="400" height="400" style="border: 1px solid green;"></canvas>
  </div>

 5.总结:

不足:当光标移动到线上时,判断一点是否在两点连成的直线上计算方法不正确,应该计算为一点是否在两点圆两条外切线所围成的矩形内;钢笔点应为替换为小的div方格比较合理,像下面的矩形抠图;(思路:将存取的点坐标集合和动态添加的小div方格建立对应关系当拖动小方格时,触发事件更新坐标点集合,并重新渲染)。

以上所述是小编给大家介绍的js+html5 canvas实现ps钢笔抠图详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
15款优秀的jQuery导航菜单插件分享
Jul 19 Javascript
在JS中解析HTML字符串示例代码
Apr 16 Javascript
js实现图片无缝滚动特效
Mar 19 Javascript
jQuery事件用法详解
Oct 06 Javascript
微信小程序 选择器(时间,日期,地区)实例详解
Nov 16 Javascript
JavaScript浏览器对象模型BOM(BrowserObjectModel)实例详解
Nov 29 Javascript
vue中遇到的坑之变化检测问题(数组相关)
Oct 13 Javascript
vue 项目打包通过命令修改 vue-router 模式 修改 API 接口前缀
Jun 13 Javascript
layui问题之模拟select点击事件的实例讲解
Aug 15 Javascript
javascript设计模式之装饰者模式
Jan 30 Javascript
vue实现分页的三种效果
Jun 23 Javascript
使用typescript快速开发一个cli的实现示例
Dec 09 Javascript
判断文字超过2行添加展开按钮,未超过则不显示,溢出部分显示省略号
Apr 28 #Javascript
详解微信小程序调用支付接口支付
Apr 28 #Javascript
Vue 中文本内容超出规定行数后展开收起的处理的实现方法
Apr 28 #Javascript
使用webpack编译es6代码的方法步骤
Apr 28 #Javascript
使用异步组件优化Vue应用程序的性能
Apr 28 #Javascript
详解微信小程序获取当前时间及日期的方法
Apr 28 #Javascript
vue自定义js图片碎片轮播图切换效果的实现代码
Apr 28 #Javascript
You might like
PHP定时执行计划任务的多种方法小结
2011/12/19 PHP
JavaScript中String和StringBuffer的速度之争
2010/04/01 Javascript
JS实现闪动的title消息提醒效果
2014/06/20 Javascript
js使用递归解析xml
2014/12/12 Javascript
Javascript实现可旋转的圆圈实例代码
2015/08/04 Javascript
easyui tree带checkbox实现单选的简单实例
2016/11/07 Javascript
js仿拉勾网首页穿墙广告效果
2017/03/08 Javascript
JavaScript仿微信(电话)联系人列表滑动字母索引实例讲解(推荐)
2017/08/16 Javascript
JS实现前端缓存的方法
2017/09/21 Javascript
浅谈ES6 模板字符串的具体使用方法
2017/11/07 Javascript
详解Vue单元测试case写法
2018/05/24 Javascript
vuex提交state&amp;&amp;实时监听state数据的改变方法
2018/09/16 Javascript
详解为生产环境编译Angular2应用的方法
2018/12/10 Javascript
JS中数组实现代码(倒序遍历数组,数组连接字符串)
2019/12/29 Javascript
vue子组件改变父组件传递的prop值通过sync实现数据双向绑定(DEMO)
2020/02/01 Javascript
javascript将16进制的字符串转换为10进制整数hex
2020/03/05 Javascript
echarts 使用formatter 修改鼠标悬浮事件信息操作
2020/07/20 Javascript
修改NPM全局模式的默认安装路径的方法
2020/12/15 Javascript
python判断字符串编码的简单实现方法(使用chardet)
2016/07/01 Python
Python处理JSON数据并生成条形图
2016/08/05 Python
Python爬取三国演义的实现方法
2016/09/12 Python
Python基础教程之浅拷贝和深拷贝实例详解
2017/07/15 Python
Python3多线程爬虫实例讲解代码
2018/01/05 Python
对django views中 request, response的常用操作详解
2019/07/17 Python
Python使用enumerate获取迭代元素下标
2020/02/03 Python
python dumps和loads区别详解
2020/02/04 Python
python如何提取英语pdf内容并翻译
2020/03/03 Python
python和go语言的区别是什么
2020/07/20 Python
详解如何在pyqt中通过OpenCV实现对窗口的透视变换
2020/09/20 Python
详解Html5微信支付爬坑之路
2018/07/24 HTML / CSS
HTML5 embed 标签使用方法介绍
2013/08/13 HTML / CSS
生物医学工程专业学生求职信范文分享
2013/12/14 职场文书
买卖车协议书
2014/04/21 职场文书
2014年党员自我评议对照检查材料
2014/09/20 职场文书
查摆问题整改措施范文
2014/10/11 职场文书
2016教师政治学习心得体会
2016/01/23 职场文书