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