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 相关文章推荐
juery框架写的弹窗效果适合新手
Nov 27 Javascript
javascript里绝对用的上的字符分割函数总结
Jul 31 Javascript
AngularJS自动表单验证
Feb 01 Javascript
javascript经典特效分享 手风琴、轮播图、图片滑动
Sep 14 Javascript
jQuery插件echarts实现的循环生成图效果示例【附demo源码下载】
Mar 04 Javascript
JavaScript使用链式方法封装jQuery中CSS()方法示例
Apr 07 jQuery
详解Vue使用 vue-cli 搭建项目
Apr 20 Javascript
深入掌握 react的 setState的工作机制
Sep 27 Javascript
React中上传图片到七牛的示例代码
Oct 10 Javascript
javascript实现抢购倒计时程序
Aug 26 Javascript
Vue + Scss 动态切换主题颜色实现换肤的示例代码
Apr 27 Javascript
微信小程序 WeUI扩展组件库的入门教程
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在网页中动态生成PDF文件详细教程
2014/07/05 PHP
PHP产生不重复随机数的5个方法总结
2014/11/12 PHP
Laravel推荐使用的十个辅助函数
2019/05/10 PHP
Jquery下的26个实用小技巧(jQuery tips, tricks &amp; solutions)
2010/03/01 Javascript
ASP 过滤数组重复数据函数(加强版)
2010/05/31 Javascript
js通过元素class名字获取元素集合的具体实现
2014/01/06 Javascript
JqueryMobile动态生成listView并实现刷新的两种方法
2014/03/05 Javascript
JS封装cookie操作函数实例(设置、读取、删除)
2015/11/17 Javascript
js实现文字垂直滚动和鼠标悬停效果
2015/12/31 Javascript
JavaScript中利用jQuery绑定事件的几种方式小结
2016/03/06 Javascript
js根据手机客户端浏览器类型,判断跳转官网/手机网站多个实例代码
2016/04/30 Javascript
jQuery中show与hide方法用法示例
2016/09/16 Javascript
jQuery EasyUI封装简化操作
2016/09/18 Javascript
JS+CSS3制作炫酷的弹窗效果
2016/11/08 Javascript
jQuery树插件zTree使用方法详解
2017/05/02 jQuery
Vue 实现拖动滑块验证功能(只有css+js没有后台验证步骤)
2018/08/24 Javascript
Node.JS在命令行中检查Chrome浏览器是否安装并打开指定网址
2019/05/21 Javascript
微信JS-SDK实现微信会员卡功能(给用户微信卡包里发送会员卡)
2019/07/25 Javascript
如何使用Javascript中的this关键字
2020/05/28 Javascript
python实现的守护进程(Daemon)用法实例
2015/06/02 Python
Python OpenCV获取视频的方法
2018/02/28 Python
python批量复制图片到另一个文件夹
2018/09/17 Python
python实现多层感知器
2019/01/18 Python
Django项目中实现使用qq第三方登录功能
2019/08/13 Python
详解Python设计模式之策略模式
2020/06/15 Python
pycharm 代码自动补全的实现方法(图文)
2020/09/18 Python
HTML5中的websocket实现直播功能
2018/05/21 HTML / CSS
一套.net面试题及答案
2016/11/02 面试题
怎样写好自我鉴定
2013/12/04 职场文书
2014年汽车销售工作总结
2014/12/01 职场文书
师德标兵先进事迹材料
2014/12/19 职场文书
银行稽核岗位职责
2015/04/13 职场文书
2015年度公共机构节能工作总结
2015/05/26 职场文书
团结友爱主题班会
2015/08/13 职场文书
导游词之苏州盘门景区
2019/11/12 职场文书
DE1107机评
2022/04/05 无线电