html5 canvas+js實現ps鋼筆摳圖(速摳圖 www.sukoutu.com)

 

html5 canvas+js實現ps鋼筆摳圖(速摳圖 www.sukoutu.com) 

 根據html5 canvas+js實現ps鋼筆摳圖的實現,aiaito 開發者開發了一套在線摳圖工具,速摳圖sukoutu.com是一款公益性質的免費在線快速摳圖工具,javascript

支持支持8倍高清鋼筆摳圖、矩陣摳圖、圖片壓縮、圖片尺寸調整等,該工具旨在爲用戶提供更快捷高效的摳圖服務。
css

 

 

1. 項目要求須要用js實現photoshop中鋼筆摳圖功能,就用了近三四天的時間去解決它,最終仍是基本上把他實現了。html

    作的過程當中走了很多彎路,最終一同事找到了canvans以比較核心的屬性globalCompositeOperation = "destination-out",html5

    屬性能夠實現經過由多個點構成的閉合區間設置成透明色穿透畫布背景色或是背景圖片,這樣省了許多事。java

2.實現效果:canvas

   鼠標點完以後會將全部的點連成閉合區間,並可自由拖拉任一點,當造成閉合區間後,可在任意兩點之間添加新點進行拖拉。數組

3.實現思路:函數

  設置兩層div,底層設置圖片,頂層設置canvas畫布(若是將圖片渲染到畫布上,摳圖時會閃爍,因此至於底層),在畫布上監視工具

  鼠標事件反覆渲染點及之間連線,造成閉合區間後將總體畫布渲染小塊背景圖片,並將閉合區間渲染透明色。並把點的相對畫布this

  座標記錄或更新到數組中去。截完圖後,將點的座標集合傳回後臺,由後臺代碼實現根據座標點及圖片寬度高度實現截圖,並設

  至背景色爲透明色(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 = 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 = "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 = "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鋼筆摳圖的一種解決方案,項目中如今這塊還在改進,若是你們有好的方法或是資料的話,但願能分享一下。謝謝

相關文章
相關標籤/搜索