首页 > 代码库 > html5 canvas+js实现ps钢笔抠图

html5 canvas+js实现ps钢笔抠图

html5 canvas+js实现ps钢笔抠图

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

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

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

2.实现效果:

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

3.实现思路:

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

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

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

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

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

  1  <script type="text/javascript">
  2         $(function () {
  3             var a = new tailorImg();
  4             a.iniData();
  5         });
  6         //
  7         var tailorImg=function()
  8         {
  9             this.iniData = http://www.mamicode.com/function () {
 10                 //画布
 11                 this.can.id = "canvas";
 12                 this.can.w = 400;
 13                 this.can.h = 400;
 14                 this.can.roundr = 7;
 15                 this.can.roundrr = 3;
 16                 this.can.curPointIndex = 0;
 17                 this.can.imgBack.src = "http://www.mamicode.com/gzf.png";
 18                 this.can.canvas = document.getElementById(this.can.id).getContext("2d");
 19                 //图片
 20                 this.img.w = 400;
 21                 this.img.h = 400;
 22                 this.img.image.src = "http://www.mamicode.com/flower.jpg";
 23                 //加载事件:
 24                 //初始化事件:
 25                 var a = this;
 26                 var p = a.can.pointList;
 27                 $("#" + a.can.id).mousemove(function (e) {
 28                     if (a.can.paint) {//是不是按下了鼠标  
 29                         if (p.length > 0) {
 30                             a.equalStartPoint(p[p.length - 1].pointx, p[p.length - 1].pointy);
 31                         }
 32                         a.roundIn(e.offsetX, e.offsetY);
 33                     }
 34                     //判断是否在直线上
 35                     //光标移动到线的附近如果是闭合的需要重新划线,并画上新添加的点
 36                     a.AddNewNode(e.offsetX, e.offsetY);
 37                 });
 38                 $("#" + a.can.id).mousedown(function (e) {
 39                     a.can.paint = true;
 40                     //点击判断是否需要在线上插入新的节点:
 41                     if (a.can.tempPointList.length > 0) {
 42                         a.can.pointList.splice(a.can.tempPointList[1].pointx, 0, new a.point(a.can.tempPointList[0].pointx, a.can.tempPointList[0].pointy));
 43                         //清空临时数组
 44                         a.can.tempPointList.length = 0;
 45                     }
 46                 });
 47                 $("#" + a.can.id).mouseup(function (e) {
 48                     //拖动结束
 49                     a.can.paint = false;
 50                     //拖动结束;
 51                     if (a.can.juPull) {
 52                         a.can.juPull = false;
 53                         a.can.curPointIndex = 0;
 54                         //验证抠图是否闭合:闭合,让结束点=开始点;添加标记
 55                         a.equalStartPoint(p[p.length - 1].pointx, p[p.length - 1].pointy);
 56                         //判断是否闭合:
 57                         if (a.can.IsClose) {
 58 
 59                         }
 60                     }
 61                     else {
 62                         //如果闭合:禁止添加新的点;
 63                         if (!a.can.IsClose) {//没有闭合
 64                             p.push(new a.point(e.offsetX, e.offsetY));
 65                             //验证抠图是否闭合:闭合,让结束点=开始点;添加标记
 66                             a.equalStartPoint(p[p.length - 1].pointx, p[p.length - 1].pointy);
 67                             //判断是否闭合:
 68                             //重新画;
 69                             if (p.length > 1) {
 70                                 a.drawLine(p[p.length - 2].pointx, p[p.length - 2].pointy, p[p.length - 1].pointx, p[p.length - 1].pointy);
 71                                 a.drawArc(p[p.length - 1].pointx, p[p.length - 1].pointy);
 72                             } else {
 73                                 a.drawArc(p[p.length - 1].pointx, p[p.length - 1].pointy);
 74                             }
 75                         }
 76                         else {
 77                             //闭合
 78                         }
 79                     }
 80                     //验证是否填充背景:
 81                     if (a.can.IsClose) {
 82                         a.fillBackColor();
 83                         a.drawAllLine();
 84                     }
 85                 });
 86                 $("#" + a.can.id).mouseleave(function (e) {
 87                     a.can.paint = false;
 88                 });
 89                 //鼠标点击事件:
 90                 $("#" + a.can.id).click(function (e) {
 91                     //
 92                 });
 93             }
 94             this.point = function (x, y) {
 95                 this.pointx = x;
 96                 this.pointy = y;
 97             };
 98             //图片
 99             this.img = {
100                 image:new Image(),
101                 id: "",
102                 w:0,
103                 h:0
104             };
105             //画布;
106             this.can = {
107                 canvas:new Object(),
108                 id: "",
109                 w: 0,
110                 h: 0,
111                 //坐标点集合
112                 pointList: new Array(),
113                 //临时存储坐标点
114                 tempPointList: new Array(),
115                 //圆点的触发半径:
116                 roundr: 7,
117                 //圆点的显示半径:
118                 roundrr: 7,
119                 //当前拖动点的索引值;
120                 curPointIndex : 0,
121                 //判断是否点击拖动
122                 paint : false,
123                 //判断是否点圆点拖动,并瞬间离开,是否拖动点;
124                 juPull : false,
125                 //判断是否闭合
126                 IsClose: false,
127                 imgBack: new Image()
128                 
129             };
130             //函数:
131             //更新画线
132             this.drawAllLine=function () {
133                 for (var i = 0; i < this.can.pointList.length - 1; i++) {
134                     //画线
135                     var p = this.can.pointList;
136                     this.drawLine(p[i].pointx, p[i].pointy, p[i + 1].pointx, p[i + 1].pointy);
137                     //画圈
138                     this.drawArc(p[i].pointx, p[i].pointy);
139                     if (i == this.can.pointList.length - 2) {
140                         this.drawArc(p[i+1].pointx, p[i+1].pointy);
141                     }
142                 }
143             }
144             //画线
145             this.drawLine = function (startX, startY, endX, endY) {
146                 //var grd = this.can.canvas.createLinearGradient(0, 0,2,0); //坐标,长宽
147                 //grd.addColorStop(0, "black"); //起点颜色
148                 //grd.addColorStop(1, "white");
149                 //this.can.canvas.strokeStyle = grd;
150                 this.can.canvas.strokeStyle = "blue"
151                 this.can.canvas.lineWidth =1;
152                 this.can.canvas.moveTo(startX, startY);
153                 this.can.canvas.lineTo(endX, endY);
154                 this.can.canvas.stroke();
155             }
156             //画圈:
157             this.drawArc=function(x, y) {
158                this.can.canvas.fillStyle = "blue";
159                 this.can.canvas.beginPath();
160                 this.can.canvas.arc(x, y,this.can.roundrr, 360, Math.PI * 2, true);
161                 this.can.canvas.closePath();
162                 this.can.canvas.fill();
163             }
164             //光标移到线上画大圈:
165             this.drawArcBig = function (x, y) {
166                 this.can.canvas.fillStyle = "blue";
167                 this.can.canvas.beginPath();
168                 this.can.canvas.arc(x, y, this.can.roundr+2, 360, Math.PI * 2, true);
169                 this.can.canvas.closePath();
170                 this.can.canvas.fill();
171             }
172             //渲染图片往画布上
173             this.showImg=function() {
174                 this.img.image.onload = function () {
175                     this.can.canvas.drawImage(this.img.image, 0, 0, this.img.w,this.img.h);
176                 };
177             }
178             //填充背景色
179             this.fillBackColor = function () {
180                 for (var i = 0; i <this.img.w; i += 96) {
181                     for (var j = 0; j <= this.img.h; j += 96) {
182                         this.can.canvas.drawImage(this.can.imgBack, i, j, 96, 96);
183                     }
184                 }
185                 this.can.canvas.globalCompositeOperation = "destination-out";
186                 this.can.canvas.beginPath();
187                 for (var i = 0; i <this.can.pointList.length; i++) {
188                     this.can.canvas.lineTo(this.can.pointList[i].pointx,this.can.pointList[i].pointy);
189                 }
190                 this.can.canvas.closePath();
191                 this.can.canvas.fill();
192                 this.can.canvas.globalCompositeOperation = "destination-over";
193                 this.drawAllLine();
194             }
195             //去掉pointlist最后一个坐标点:
196             this.clearLastPoint=function () {
197                 this.can.pointList.pop();
198                 //重画:
199                 this.clearCan();
200                 this.drawAllLine();
201             }
202             //判断结束点是否与起始点重合;
203             this.equalStartPoint = function (x,y) {
204                 var p = this.can.pointList;
205                 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) {
206                     //如果闭合
207                     this.can.IsClose = true;
208                     p[p.length - 1].pointx = p[0].pointx;
209                     p[p.length - 1].pointy = p[0].pointy;
210                 }
211                 else {
212                     this.can.IsClose = false;
213                 }
214             }
215             //清空画布
216             this.clearCan=function (){
217                 this.can.canvas.clearRect(0, 0, this.can.w, this.can.h);
218             }
219             //剪切区域
220             this.CreateClipArea=function () {
221                 this.showImg();
222                 this.can.canvas.beginPath();
223                 for (var i = 0; i <this.can.pointList.length; i++) {
224                     this.can.canvas.lineTo(this.can.pointList[i].pointx,this.can.pointList[i].pointy);
225                 }
226                 this.can.canvas.closePath();
227                 this.can.canvas.clip();
228             }
229             //
230             this.CreateClipImg=function()
231             {
232 
233             }
234             //判断鼠标点是不是在圆的内部:
235             this.roundIn = function (x, y) {
236                 //刚开始拖动
237                 var p = this.can.pointList;
238                 if (!this.can.juPull) {
239                     for (var i = 0; i < p.length; i++) {
240 
241                         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) {
242                             //说明点击圆点拖动了;
243                             this.can.juPull = true;//拖动
244                             //
245                             this.can.curPointIndex = i;
246                             p[i].pointx = x;
247                             p[i].pointy = y;
248                             //重画:
249                             this.clearCan();
250                             //showImg();
251                             if (this.can.IsClose) {
252                                 this.fillBackColor();
253                             }
254                             this.drawAllLine();
255                             return;
256                         }
257                     }
258                 }
259                 else {//拖动中
260                     p[this.can.curPointIndex].pointx = x;
261                     p[this.can.curPointIndex].pointy = y;
262                     //重画:
263                     this.clearCan();
264                     if (this.can.IsClose) {
265                         this.fillBackColor();
266                     }
267                     this.drawAllLine();
268                 }
269             };
270 
271             //光标移到线上,临时数组添加新的节点:
272            this.AddNewNode=function(newx, newy) {
273                //如果闭合
274                var ii=0;
275                 if (this.can.IsClose) {
276                     //判断光标点是否在线上:
277                     var p = this.can.pointList;
278                     for (var i = 0; i < p.length - 1; i++) {
279                         //计算a点和b点的斜率
280                         var k = (p[i + 1].pointy - p[i].pointy) / (p[i + 1].pointx - p[i].pointx);
281                         var b = p[i].pointy - k * p[i].pointx;
282                         //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) {
283                         //    //如果在直线上
284                         //    alert("在直线上");
285                         //}
286                         $("#txtone").val(parseInt(k * newx + b));
287                         $("#txttwo").val(parseInt(newy));
288                         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) {
289                             //
290                             //parseInt(k * newx + b) == parseInt(newy)
291                             //添加临时点:
292                             this.can.tempPointList[0] = new this.point(newx, newy);//新的坐标点
293                             this.can.tempPointList[1] = new this.point(i+1, i+1);//需要往pointlist中插入新点的索引;
294                             i++;
295                             //alert();
296                             //光标移动到线的附近如果是闭合的需要重新划线,并画上新添加的点;
297                             if (this.can.tempPointList.length > 0) {
298                                 //重画:
299                                 this.clearCan();
300                                 //showImg();
301                                 if (this.can.IsClose) {
302                                     this.fillBackColor();
303                                 }
304                                 this.drawAllLine();
305                                 this.drawArcBig(this.can.tempPointList[0].pointx, this.can.tempPointList[0].pointy);
306                                 return;
307                             }
308                             return;
309                         }
310                         else {
311                            // $("#Text1").val("");
312                         }
313                     }
314                     if (ii == 0) {
315                         if (this.can.tempPointList.length > 0) {
316                             //清空临时数组;
317                             this.can.tempPointList.length = 0;
318                             //重画:
319                             this.clearCan();
320                             //showImg();
321                             if (this.can.IsClose) {
322                                 this.fillBackColor();
323                             }
324                             this.drawAllLine();
325                             //this.drawArc(this.can.tempPointList[0].pointx, this.can.tempPointList[0].pointy);
326                         }
327                     }
328                 }
329                 else {
330                     //防止计算误差引起的添加点,当闭合后,瞬间移动起始点,可能会插入一个点到临时数组,当再次执行时,
331                     //就会在非闭合情况下插入该点,所以,时刻监视:
332                     if (this.can.tempPointList.length > 0) {
333                         this.can.tempPointList.length = 0;
334                     }
335                 }
336            }
337             
338         };
339 
340     </script>
 1  <style type="text/css">
 2         .canvasDiv {
 3             position: relative;
 4             border: 1px solid red;
 5             height: 400px;
 6             width: 400px;
 7             top: 50px;
 8             left: 100px;
 9             z-index: 0;
10         }
11 
12         img {
13             width: 400px;
14             height: 400px;
15             z-index: 1;
16             position: absolute;
17         }
18 
19         #canvas {
20             position: absolute;
21             border: 1px solid green;
22             z-index: 2;
23         }
24         .btnCollection {
25             margin-left: 100px;
26         }
27     </style>
1  <div class="canvasDiv">
2             <img src="flower.jpg" />
3             <canvas id="canvas" width="400" height="400" style="border: 1px solid green;"></canvas>
4         </div>

5.总结:

   不足:当光标移动到线上时,判断一点是否在两点连成的直线上计算方法不正确,应该计算为一点是否在两点圆两条外切线所围成的矩形

           内;钢笔点应为替换为小的div方格比较合理,像下面的矩形抠图;(思路:将存取的点坐标集合和动态添加的小div方格建立对应关系

           当拖动小方格时,触发事件更新坐标点集合,并重新渲染)。

           

6.这只是js钢笔抠图的一种解决方案,项目中现在这块还在改进,如果大家有好的方法或是资料的话,希望能分享一下。谢谢