>  기사  >  웹 프론트엔드  >  html5 canvas+js는 PS 펜 컷아웃을 구현합니다.

html5 canvas+js는 PS 펜 컷아웃을 구현합니다.

高洛峰
高洛峰원래의
2017-02-14 10:08:052520검색

html5 canvas+js가 PS에서 펜 컷아웃을 구현합니다

1. 프로젝트 요구 사항에 따라 포토샵에서 펜 컷아웃 기능을 구현하려면 js를 사용해야 하는데 거의 3~4일이 걸렸습니다. 결국 그것은 기본적으로 그를 위해 실현되었습니다.

그 과정에서 많은 우회를 했고, 마침내 동료가 핵심 속성을 비교하기 위해 캔버스를 찾았습니다. globalCompositeOperation = "destination-out",

여러 점으로 구성하여 속성을 구현할 수 있습니다. 닫힌 범위는 캔버스 배경색이나 배경 이미지를 투과하는 투명한 색상으로 설정되어 있어 작업량이 많이 절약됩니다.

2. 실현 효과:

마우스를 클릭하면 모든 지점이 닫힌 간격으로 연결되며, 어떤 지점이든 자유롭게 드래그할 수 있습니다. 두 점 사이 드래그하여 새 점을 추가합니다.

html5 canvas+js实现ps钢笔抠图html5 canvas+js实现ps钢笔抠图html5 canvas+js实现ps钢笔抠图html5 canvas+js实现ps钢笔抠图

3. 구현 아이디어:

p 레이어를 두 개 설정하고, 맨 아래 레이어에 그림을 설정하고, 그리고 상위 레이어에 캔버스를 설정하고 (이미지를 캔버스에 렌더링하면 잘라낼 때 깜박이므로 하위 레이어로 이동) 캔버스에서

을 모니터링하면 마우스 이벤트가 반복적으로 렌더링됩니다. 점과 그 사이의 선을 연결하여 닫힌 간격을 형성한 다음 캔버스 전체를 작은 배경 이미지로 렌더링하고 닫힌 간격을 투명한 색상으로 렌더링합니다. 그리고 점의 상대 캔버스

좌표를 배열에 기록하거나 업데이트합니다. 사진 촬영 후 포인트의 좌표 세트를 다시 배경으로 보내고 배경 코드는 좌표 포인트와 사진의 너비, 높이에 따라 스크린샷을 구현하고

을 배경으로 설정합니다. color as transparent (캔버스도 스크린샷을 구현할 수 있지만 배경 투명도를 얻으려면 픽셀 처리가 필요하며 아직 구현되지 않았습니다. 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>
rrree

요약:

단점: 커서가 해당 줄로 이동할 때, 판단해 보세요. 두 점으로 연결된 직선 위에 있는지 여부 계산 방법이 올바르지 않습니다. 두 점의 두 외부 접선으로 둘러싸인 직사각형

내에 있는지 여부를 계산해야 합니다. 원; 펜 포인트를 작은 p 정사각형으로 교체해야 합니다. 아래 직사각형 컷아웃과 같이 그리드가 더 합리적입니다. (아이디어: 액세스된 포인트 좌표 세트와 동적으로 추가된 작은 p 정사각형 사이의 대응 관계를 설정하십시오.

작은 사각형을 드래그하면 이벤트가 발생하여 좌표점을 업데이트하고 다시 렌더링합니다.

html5 canvas+js实现ps钢笔抠图

6. 이것은 js 펜 컷아웃에 대한 솔루션일 뿐입니다. 이 부분은 아직 개선 중입니다. 좋은 방법이나 정보가 있으면 공유해 주세요. 그것. 감사합니다

PS 펜 컷아웃 관련 기사의 더 많은 html5 canvas+js 구현을 보려면 PHP 중국어 웹사이트를 주목하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.