1. 项目要求需要用js实现photoshop中钢笔抠图功能,就用了近三四天的时间去解决它,最终还是基本上把他实现了。
做的过程中走了不少弯路,最终一同事找到了canvans以比较核心的属性globalCompositeOperation = "destination-out",
属性可以实现通过由多个点构成的闭合区间设置成透明色穿透画布背景色或是背景图片,这样省了许多事。
2.实现效果:
鼠标点完之后会将所有的点连成闭合区间,并可自由拖拉任一点,当形成闭合区间后,可在任意两点之间添加新点进行拖拉。
3.实现思路:
设置两层p,底层设置图片,顶层设置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>总结:
不足:当光标移动到线上时,判断一点是否在两点连成的直线上计算方法不正确,应该计算为一点是否在两点圆两条外切线所围成的矩形
内;钢笔点应为替换为小的p方格比较合理,像下面的矩形抠图;(思路:将存取的点坐标集合和动态添加的小p方格建立对应关系
当拖动小方格时,触发事件更新坐标点集合,并重新渲染)。
6.这只是js钢笔抠图的一种解决方案,项目中现在这块还在改进,如果大家有好的方法或是资料的话,希望能分享一下。谢谢
更多html5 canvas+js实现ps钢笔抠图相关文章请关注PHP中文网!
Photoshop默认保存的文件格式,可以保留所有有图层、色版、通道、蒙版、路径、未栅格化文字以及图层样式等。
……