html5 canvas+js实现ps钢笔抠图(速抠图 www.sukoutu.com) 

 根据html5 canvas+js实现ps钢笔抠图的实现,aiaito 开发者开发了一套在线抠图工具,速抠图sukoutu.com是一款公益性质的免费在线快速抠图工具,

支持支持8倍高清钢笔抠图、矩阵抠图、图片压缩、图片尺寸调整等,该工具旨在为用户提供更快捷高效的抠图服务。

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

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

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

2.实现效果:

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

3.实现思路:

设置两层div,底层设置图片,顶层设置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:;
} img {
width: 400px;
height: 400px;
z-index:;
position: absolute;
} #canvas {
position: absolute;
border: 1px solid green;
z-index:;
}
.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>

5.总结:

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

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

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

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

html5 canvas+js实现ps钢笔抠图的更多相关文章

  1. html5 canvas+js实现ps钢笔抠图(速抠图 www.sukoutu.com)

    html5 canvas+js实现ps钢笔抠图(速抠图 www.sukoutu.com)   根据html5 canvas+js实现ps钢笔抠图的实现,aiaito 开发者开发了一套在线抠图工具,速抠 ...

  2. [ZZ+CH] Html5 canvas+js 时钟

    总之新Blog入驻以后,又开始老习惯,到处折腾自定义的空间,放些东西. 想起以前大一的时候做过一个Javascript的时间显示器,现在想做一个时钟,当然现在老奸巨猾,会先去看一看有前辈写过没. 前辈 ...

  3. html5 canvas js(时钟)

    <!doctype html> <html> <head> <title>canvas</title> </head> < ...

  4. html5 canvas js(数字时钟)

      <!doctype html> <html> <head> <title>canvas dClock</title> </head ...

  5. HTML5 Canvas JavaScript库 Fabric.js 使用经验

    首先,表明我的态度:采用 Flash 才是最优方案,不建议使用 HTML 5 的 Canvas 做一些生产/工业级的网页应用. Flash的优势一是浏览器支持好,二是代码成熟稳定.而HTML5 的 C ...

  6. 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理

    [微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...

  7. 用html5 canvas和JS写个数独游戏

    为啥要写这个游戏? 因为我儿子二年级数字下册最后一章讲到了数独.他想玩儿. 因为我也想玩有提示功能的数独. 因为我也正想决定要把HTML5和JS搞搞熟.熟悉一个编程平台,最好的办法,就是了解其原理与思 ...

  8. 超酷HTML5 Canvas图表应用Chart.js自定义提示折线图

    超酷HTML5 Canvas图表应用Chart.js自定义提示折线图 效果预览 实例代码 <div class="htmleaf-container"> <div ...

  9. 基于html5 canvas和js实现的水果忍者网页版

    今天爱编程小编给大家分享一款基于html5 canvas和js实现的水果忍者网页版. <水果忍者>是一款非常受喜欢的手机游戏,刚看到新闻说<水果忍者>四周年新版要上线了.网页版 ...

随机推荐

  1. hibernate 管理 Session(单独使用session,不spring)

    Hibernate 本身提供了三个管理 Session 对象的方法 Session 对象的生命周期与本地线程绑定 Session 对象的生命周期与 JTA 事务绑定 Hibernate 托付程序管理 ...

  2. hdu 1098 Ignatius's puzz

    有关数论方面的题要仔细阅读,分析公式. Problem Description Ignatius is poor at math,he falls across a puzzle problem,so ...

  3. Linux makefile 课程 非常具体的,和理解

    最近的一项研究Linux根据C计划,我买了一个电话<Linux环境C编程指南>阅读makefile这使他看起来困惑,我可能无法理解. 于是google到了下面这篇文章. 通俗易懂. 然后把 ...

  4. Java 递归算法

    其基本思路是递归算法设计:对于一个复杂的问题,原问题分为几个子问题相似相对简单.继续下去,直到孩子可以简单地解决问题,这是导出复发,因此,有复发的原始问题已经解决. 关键是要抓住: (1)递归出口 ( ...

  5. Redhat Linux挂载NTFS格式的移动硬盘

    Redhat Linux挂载NTFS格式的移动硬盘 1. 选择下载ntfs-3g的源码包或rpm包 http://www.tuxera.com/community/open-source-ntfs-3 ...

  6. 程序员的Scala

    C#程序员的Scala之路第九章(Scala的层级) 摘要: 1.Scala的类层级Scala里类的顶端是Any所有的类都继承Any类,Any包括以下几个通用方法:final def ==(that: ...

  7. Codeforces 328B-Sheldon and Ice Pieces(馋)

    B. Sheldon and Ice Pieces time limit per test 1 second memory limit per test 256 megabytes input sta ...

  8. ssh 自动登录

    工作中经常会有这样的需求场景,因为要在其它电脑上做操作, 需要从PC A ssh 到 PC B,PC A 可能是自己的工作机,PC B 可能是服务器.一般会使用 SSH 登录到 server 上再进行 ...

  9. Factorization Machines 学习笔记(二)模型方程

      近期学习了一种叫做 Factorization Machines(简称 FM)的算法,它可对随意的实值向量进行预測.其主要长处包含: 1) 可用于高度稀疏数据场景:2) 具有线性的计算复杂度.本文 ...

  10. JavasScript实现调查问卷插件

    原文:JavasScript实现调查问卷插件 鄙人屌丝程序猿一枚,闲来无事,想尝试攻城师是感觉,于是乎搞了点小玩意.用js实现调查问卷,实现了常规的题型,单选,多选,排序,填空,矩阵等. 遂开源贴出来 ...