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 相关文章推荐
jquery 全局AJAX事件使用代码
Nov 05 Javascript
基于JQuery的模拟苹果桌面Dock效果(稳定版)
Oct 15 Javascript
移动设备web开发首选框架:zeptojs介绍
Jan 29 Javascript
js鼠标点击图片实现随机变换图片的方法
Feb 16 Javascript
jQuery读取XML文件内容的方法
Mar 09 Javascript
AngularJS中directive指令使用之事件绑定与指令交互用法示例
Nov 22 Javascript
react中的ajax封装实例详解
Oct 17 Javascript
极简主义法编写JavaScript类
Nov 02 Javascript
JS实现获取自定义属性data值的方法示例
Dec 19 Javascript
react高阶组件添加和删除props
Apr 26 Javascript
uni-app实现点赞评论功能
Nov 25 Javascript
教你一步步实现一个简易promise
Nov 02 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
DC动漫人物排行
2020/03/03 欧美动漫
PHP之正则表达式捕获组与非捕获组(详解)
2015/07/29 PHP
php使用lua+redis实现限流,计数器模式,令牌桶模式
2019/04/04 PHP
用PHP做了一个领取优惠券活动的示例代码
2019/07/05 PHP
用CSS+JS实现的进度条效果效果
2007/06/05 Javascript
json对象转字符串如何实现
2012/12/02 Javascript
jquery css 设置table的奇偶行背景色示例
2014/06/03 Javascript
javascript中attachEvent用法实例分析
2015/05/14 Javascript
javascript实现文字无缝滚动
2016/12/27 Javascript
bootstrap+jQuery实现的动态进度条功能示例
2017/05/25 jQuery
AngularJS中控制器函数的定义与使用方法示例
2017/10/10 Javascript
react-navigation 如何判断用户是否登录跳转到登录页的方法
2017/12/01 Javascript
使用Angular CLI从蓝本生成代码详解
2018/03/24 Javascript
详解JQuery基础动画操作
2019/04/12 jQuery
[03:54]DOTA2英雄梦之声_第06期_昆卡
2014/06/23 DOTA
python模块restful使用方法实例
2013/12/10 Python
python3+PyQt5+Qt Designer实现堆叠窗口部件
2018/04/20 Python
初探利用Python进行图文识别(OCR)
2019/02/26 Python
Python 2/3下处理cjk编码的zip文件的方法
2019/04/26 Python
django 实现celery动态设置周期任务执行时间
2019/11/19 Python
Python插入Elasticsearch操作方法解析
2020/01/19 Python
python实现word文档批量转成自定义格式的excel文档的思路及实例代码
2020/02/21 Python
Python3爬虫里关于识别微博宫格验证码的知识点详解
2020/07/30 Python
python Xpath语法的使用
2020/11/26 Python
HTML5 SEO优化的一些建议
2020/08/27 HTML / CSS
优瑞自动咖啡机官网:Jura
2018/09/29 全球购物
幼教简历自我评价
2014/01/28 职场文书
学生党员一帮一活动总结
2014/07/08 职场文书
班主任师德师风自我剖析材料
2014/10/02 职场文书
2014年卫生保健工作总结
2014/12/08 职场文书
2014年科协工作总结
2014/12/09 职场文书
办公室行政主管岗位职责
2015/04/09 职场文书
2015年话务员工作总结
2015/04/29 职场文书
关于环保的广播稿
2015/12/17 职场文书
建筑工程挂靠协议书
2016/03/23 职场文书
Mysql数据库事务的脏读幻读及不可重复读详解
2022/05/30 MySQL