Jadual Kandungan
html5 canvas+js实现ps钢笔抠图
Rumah hujung hadapan web Tutorial PS html5 canvas+js实现ps钢笔抠图

html5 canvas+js实现ps钢笔抠图

Feb 14, 2017 am 10:08 AM

html5 canvas+js实现ps钢笔抠图

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

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

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

2.实现效果:

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

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

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>
Salin selepas log masuk
<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>
Salin selepas log masuk
<div class="canvasDiv">
<img src="flower.jpg" />
<canvas id="canvas" width="400" height="400" style="border: 1px solid green;"></canvas>
</div>
Salin selepas log masuk

总结:

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

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

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

           html5 canvas+js实现ps钢笔抠图

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

 更多html5 canvas+js实现ps钢笔抠图相关文章请关注PHP中文网!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

AI Hentai Generator

AI Hentai Generator

Menjana ai hentai secara percuma.

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Apakah sebab mengapa PS terus menunjukkan pemuatan? Apakah sebab mengapa PS terus menunjukkan pemuatan? Apr 06, 2025 pm 06:39 PM

PS "Memuatkan" Masalah disebabkan oleh akses sumber atau masalah pemprosesan: Kelajuan bacaan cakera keras adalah perlahan atau buruk: Gunakan CrystaldiskInfo untuk memeriksa kesihatan cakera keras dan menggantikan cakera keras yang bermasalah. Memori yang tidak mencukupi: Meningkatkan memori untuk memenuhi keperluan PS untuk imej resolusi tinggi dan pemprosesan lapisan kompleks. Pemandu kad grafik sudah lapuk atau rosak: Kemas kini pemandu untuk mengoptimumkan komunikasi antara PS dan kad grafik. Laluan fail terlalu panjang atau nama fail mempunyai aksara khas: Gunakan laluan pendek dan elakkan aksara khas. Masalah PS sendiri: Pasang semula atau membaiki pemasang PS.

Apakah soalan umum mengenai mengeksport PDF di PS Apakah soalan umum mengenai mengeksport PDF di PS Apr 06, 2025 pm 04:51 PM

Soalan dan penyelesaian yang sering ditanya Apabila mengeksport PS sebagai PDF: Font Embedding Masalah: Periksa pilihan "Font", pilih "Benamkan" atau tukar fon ke dalam lengkung (laluan). Masalah Penyimpangan Warna: Tukar fail ke dalam mod CMYK dan laraskan warna; Secara langsung mengeksportnya dengan RGB memerlukan persediaan psikologi untuk pratonton dan sisihan warna. Isu Resolusi dan Saiz Fail: Pilih resolusi mengikut keadaan sebenar, atau gunakan pilihan mampatan untuk mengoptimumkan saiz fail. Isu Kesan Khas: Gabungan (meratakan) lapisan sebelum mengeksport, atau menimbang kebaikan dan keburukan.

Bagaimana untuk menyelesaikan masalah pemuatan apabila PS sentiasa menunjukkan bahawa ia memuatkan? Bagaimana untuk menyelesaikan masalah pemuatan apabila PS sentiasa menunjukkan bahawa ia memuatkan? Apr 06, 2025 pm 06:30 PM

Kad PS adalah "Memuatkan"? Penyelesaian termasuk: Memeriksa konfigurasi komputer (memori, cakera keras, pemproses), membersihkan pemecahan cakera keras, mengemas kini pemacu kad grafik, menyesuaikan tetapan PS, memasang semula PS, dan membangunkan tabiat pengaturcaraan yang baik.

Cara menetapkan perlindungan kata laluan untuk pdf eksport di ps Cara menetapkan perlindungan kata laluan untuk pdf eksport di ps Apr 06, 2025 pm 04:45 PM

Eksport PDF yang dilindungi kata laluan di Photoshop: Buka fail imej. Klik "Fail" & gt; "Eksport" & gt; "Eksport sebagai PDF". Tetapkan pilihan "Keselamatan" dan masukkan kata laluan yang sama dua kali. Klik "Eksport" untuk menghasilkan fail PDF.

Bagaimana untuk mempercepatkan kelajuan pemuatan PS? Bagaimana untuk mempercepatkan kelajuan pemuatan PS? Apr 06, 2025 pm 06:27 PM

Menyelesaikan masalah Permulaan Photoshop Perlahan memerlukan pendekatan berbilang arah, termasuk: menaik taraf perkakasan (memori, pemacu keadaan pepejal, CPU); menyahpasang pemalam yang sudah lapuk atau tidak serasi; membersihkan sampah sistem dan program latar belakang yang berlebihan dengan kerap; menutup program yang tidak relevan dengan berhati -hati; Mengelakkan membuka sejumlah besar fail semasa permulaan.

Bagaimana untuk menyelesaikan masalah pemuatan apabila PS membuka fail? Bagaimana untuk menyelesaikan masalah pemuatan apabila PS membuka fail? Apr 06, 2025 pm 06:33 PM

"Memuatkan" gagap berlaku apabila membuka fail pada PS. Sebab-sebabnya mungkin termasuk: fail yang terlalu besar atau rosak, memori yang tidak mencukupi, kelajuan cakera keras perlahan, masalah pemacu kad grafik, versi PS atau konflik plug-in. Penyelesaiannya ialah: Semak saiz fail dan integriti, tingkatkan memori, menaik taraf cakera keras, mengemas kini pemacu kad grafik, menyahpasang atau melumpuhkan pemalam yang mencurigakan, dan memasang semula PS. Masalah ini dapat diselesaikan dengan berkesan dengan memeriksa secara beransur -ansur dan memanfaatkan tetapan prestasi PS yang baik dan membangunkan tabiat pengurusan fail yang baik.

Cara Menggunakan Alat Pen PS Cara Menggunakan Alat Pen PS Apr 06, 2025 pm 10:15 PM

Alat pen adalah alat yang mewujudkan laluan dan bentuk yang tepat, dan digunakan oleh: pilih alat pen (p). Menetapkan laluan, mengisi, strok, dan pilihan bentuk. Klik Buat Titik Anchor, seret lengkung untuk melepaskan titik Create Anchor. Tekan CTRL/CMD ALT/OPT untuk memadam titik utama, seret dan gerakkan titik utama, dan klik Curve Curve. Klik Anchor pertama untuk menutup jalan untuk membuat bentuk, dan klik dua kali untuk membuat laluan terbuka.

Bagaimanakah Feathering PS mengawal kelembutan peralihan? Bagaimanakah Feathering PS mengawal kelembutan peralihan? Apr 06, 2025 pm 07:33 PM

Kunci kawalan bulu adalah memahami sifatnya secara beransur -ansur. PS sendiri tidak menyediakan pilihan untuk mengawal lengkung kecerunan secara langsung, tetapi anda boleh melaraskan radius dan kelembutan kecerunan dengan pelbagai bulu, topeng yang sepadan, dan pilihan halus untuk mencapai kesan peralihan semula jadi.

See all articles