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 相关文章推荐
js中top/parent/frame概述及案例应用
Feb 06 Javascript
JS异常处理的一个想法(sofish)
Mar 14 Javascript
jQuery+ajax实现鼠标单击修改内容的思路
Jun 29 Javascript
jQuery简单自定义图片轮播插件及用法示例
Nov 21 Javascript
微信小程序 石头剪刀布实例代码
Jan 04 Javascript
vue2.0.js的多级联动选择器实现方法
Feb 09 Javascript
js中async函数结合promise的小案例浅析
Apr 14 Javascript
vue轮播组件实现$children和$parent 附带好用的gif录制工具
Sep 26 Javascript
微信小程序使用echarts获取数据并生成折线图
Oct 16 Javascript
angular异步验证防抖踩坑实录
Dec 01 Javascript
JS通过识别id、value值对checkbox设置选中状态
Feb 19 Javascript
Ajax是什么?Ajax高级用法之Axios技术
Apr 21 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发送html格式文本邮件的方法
2015/06/10 PHP
PHP框架性能测试报告
2016/05/08 PHP
浅谈mysql_query()函数的返回值问题
2016/09/05 PHP
PHP PDOStatement::bindParam讲解
2019/01/30 PHP
javascript 写类方式之二
2009/07/05 Javascript
javascript 字符 Escape,encodeURI,encodeURIComponent
2009/07/09 Javascript
JavaScript Chart 插件整理
2010/06/18 Javascript
javascript开发技术大全-第3章 js数据类型
2011/07/03 Javascript
基于jquery编写的放大镜插件
2016/03/23 Javascript
jQuery插件实现文件上传功能(支持拖拽)
2020/08/27 Javascript
Dropzone.js实现文件拖拽上传功能(附源码下载)
2016/11/22 Javascript
js获取浏览器和屏幕的各种宽度高度
2017/02/22 Javascript
Vue通过input筛选数据
2020/10/26 Javascript
DVA框架统一处理所有页面的loading状态
2017/08/25 Javascript
node通过express搭建自己的服务器
2017/09/30 Javascript
详解如何让Express支持async/await
2017/10/09 Javascript
nvm、nrm、npm 安装和使用详解(小结)
2019/01/17 Javascript
详解vue-cli3开发Chrome插件实践
2019/05/29 Javascript
Vue 实现点击空白处隐藏某节点的三种方式(指令、普通、遮罩)
2019/10/23 Javascript
Python查询Mysql时返回字典结构的代码
2012/06/18 Python
Python使用迭代器捕获Generator返回值的方法
2017/04/05 Python
详解Python sys.argv使用方法
2019/05/10 Python
如何运行.ipynb文件的图文讲解
2019/06/27 Python
Python实现性能自动化测试竟然如此简单
2019/07/30 Python
python开发入门——set的使用
2020/09/03 Python
Python基于callable函数检测对象是否可被调用
2020/10/16 Python
师范学院毕业生求职信范文
2013/12/26 职场文书
大学秋游活动方案
2014/02/11 职场文书
护士演讲稿优秀范文
2014/04/30 职场文书
房屋维修协议书范本
2014/09/25 职场文书
房屋产权共有协议书范本
2014/11/03 职场文书
2015教师年度工作总结范文
2015/04/07 职场文书
中标通知书
2015/04/17 职场文书
2015年小学数学教研组工作总结
2015/05/21 职场文书
用Python制作灯光秀短视频的思路详解
2021/04/13 Python
Win10/Win11 任务栏替换成经典样式
2022/04/19 数码科技