diff --git a/demo/findBreakPoints.html b/demo/findBreakPoints.html
new file mode 100644
index 0000000..e52e7c4
--- /dev/null
+++ b/demo/findBreakPoints.html
@@ -0,0 +1,65 @@
+
+
+
+
+ 查找拐点
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/fitImage.html b/demo/fitImage.html
new file mode 100644
index 0000000..52b268e
--- /dev/null
+++ b/demo/fitImage.html
@@ -0,0 +1,50 @@
+
+
+
+
+ canvas拟合图像
+
+
+
+
+
+
+
+
+
+ 灰度阈值:
+
+
+
+
+
+
+
+
+
diff --git a/demo/js/canvasFindBreakPoint.js b/demo/js/canvasFindBreakPoint.js
index aeb8acf..5942189 100644
--- a/demo/js/canvasFindBreakPoint.js
+++ b/demo/js/canvasFindBreakPoint.js
@@ -114,7 +114,7 @@ define(
else if (p.inflexion) {
ctx.fillStyle = 'blue';
}
- ctx.fillRect(p.x, p.y, p.right == 1 ? 4 : 2, p.right == 1 ? 4 : 2);
+ ctx.fillRect(p.x, p.y, p.right == 1 ? 6 : 3, p.right == 1 ? 6 : 3);
});
}
@@ -215,7 +215,7 @@ define(
refresh();
}
- img.src = '../test/rw1.jpg';
+ img.src = '../test/qg.gif';
}
};
diff --git a/demo/js/findBreakPoints.js b/demo/js/findBreakPoints.js
new file mode 100644
index 0000000..e30614c
--- /dev/null
+++ b/demo/js/findBreakPoints.js
@@ -0,0 +1,75 @@
+/**
+ * @file 寻找关键点
+ * @author mengke01(kekee000@gmail.com)
+ */
+
+define(
+ function (require) {
+ var fitBezier = require('graphics/image/contour/fitBezier');
+ var findBreakPoints = require('graphics/image/contour/findBreakPoints');
+ var pathUtil = require('graphics/pathUtil');
+ var data = require('demo/../data/image-contours3');
+ var drawPath = require('render/util/drawPath');
+
+ var entry = {
+
+ /**
+ * 初始化
+ */
+ init: function () {
+ var breakPoints = [];
+ var html = '';
+
+ data.forEach(function(c) {
+ c.splice(c.length - 1, 1);
+ });
+
+
+ data.forEach(function (contour) {
+ contour.forEach(function(p) {
+ html += '';
+ });
+ });
+
+ $('#points').html(html);
+
+ data.forEach(function (contour) {
+ contour = pathUtil.scale(contour, 10);
+ var points = findBreakPoints(contour, 10);
+ if (points) {
+ points.forEach(function (p) {
+ breakPoints.push(p);
+ });
+ }
+ contour = pathUtil.scale(contour, 0.1);
+ });
+
+ breakPoints.forEach(function (p) {
+ console.log(p.right);
+ });
+
+ html = '';
+ for (var i = 0, l = breakPoints.length; i < l; i++) {
+ var c = "break";
+ if (breakPoints[i].tangency) {
+ c = 'tangency';
+ }
+ else if(breakPoints[i].inflexion) {
+ c = 'inflexion';
+ }
+ var width = '';
+ if (breakPoints[i].right == 1) {
+ width = 'width: 4px;height: 4px';
+ }
+ html += '';
+ }
+
+ $('#points-break').html(html);
+ }
+ };
+
+ entry.init();
+
+ return entry;
+ }
+);
diff --git a/demo/js/fitImage.js b/demo/js/fitImage.js
new file mode 100644
index 0000000..ae22358
--- /dev/null
+++ b/demo/js/fitImage.js
@@ -0,0 +1,139 @@
+/**
+ * @file canvas读取图片
+ * @author mengke01(kekee000@gmail.com)
+ */
+
+define(
+ function (require) {
+
+ var lang = require('common/lang');
+
+ var ImageProcessor = require('graphics/image/ImageProcessor');
+ var ContourPointsProcessor = require('graphics/image/ContourPointsProcessor');
+ var pathsUtil = require('graphics/pathsUtil');
+ var editor = require('editor/main');
+
+ var ctx = null;
+ var canvas = null;
+ var curImage = null;
+
+ function onUpFileChange(e) {
+ var file = e.target.files[0];
+ var reader = new FileReader();
+ reader.onload = function(e) {
+
+ var image = curImage = new Image();
+ image.onload = function () {
+ getContours(image);
+ };
+
+ image.src = e.target.result;
+ }
+
+ reader.onerror = function(e) {
+ console.error(e);
+ };
+
+ reader.readAsDataURL(file);
+ }
+
+ function getContours(image) {
+ ctx.clearRect(0,0, canvas.width, canvas.height);
+ var width = image.width;
+ var height = image.height;
+ canvas.width = image.width;
+ canvas.height = image.height;
+
+ ctx.drawImage(image, 0, 0, width, height);
+ var imgData = ctx.getImageData(0, 0, width, height);
+ var result = new ImageProcessor(imgData).binarize(+$('#threshold-gray').val()).get();
+
+ var putData = imgData.data;
+ for (var y = 0; y < height; y ++) {
+ var line = width * y;
+ for (var x = 0; x < width; x++) {
+ var offset = line + x;
+ if (result.data[offset] === 0) {
+ putData[offset * 4] = 208;
+ putData[offset * 4 + 1] = 247;
+ putData[offset * 4 + 2] = 113;
+ putData[offset * 4 + 3] = 255;
+ }
+ else {
+ putData[offset * 4] = 255;
+ putData[offset * 4 + 1] = 255;
+ putData[offset * 4 + 2] = 255;
+ putData[offset * 4 + 3] = 255;
+ }
+ }
+ }
+ contourProcessor = new ContourPointsProcessor();
+ contourProcessor.import(result);
+ var contours = contourProcessor.get();
+
+ contours.forEach(function (contour) {
+ contour.forEach(function (p) {
+ var offset = p.y * width + p.x;
+ putData[offset * 4] = 255;
+ putData[offset * 4 + 1] = 0;
+ putData[offset * 4 + 2] = 0;
+ putData[offset * 4 + 3] = 255;
+ });
+ });
+ ctx.putImageData(imgData, 0, 0);
+
+ getBreakPoint();
+
+ var contours = contourProcessor.getContours();
+ window.editor.setFont({
+ contours: pathsUtil.flip(contours)
+ });
+ }
+
+
+ function getBreakPoint() {
+ var breakPoints = contourProcessor.getBreakPoints();
+ breakPoints.forEach(function (p) {
+ ctx.beginPath();
+ if (p.breakPoint) {
+ ctx.fillStyle = 'red';
+ }
+ else if (p.inflexion) {
+ ctx.fillStyle = 'blue';
+ }
+ ctx.fillRect(p.x, p.y, p.right == 1 ? 6 : 3, p.right == 1 ? 6 : 3);
+ });
+
+ }
+
+ function refresh() {
+ curImage && getContours(curImage);
+ }
+
+ var entry = {
+
+ /**
+ * 初始化
+ */
+ init: function () {
+
+ window.editor = editor.create($('#render-view').get(0));
+ document.getElementById('upload-file').addEventListener('change', onUpFileChange);
+ canvas = document.getElementById("canvas");
+ ctx = canvas.getContext("2d");
+ $('#threshold-gray').on('change', refresh);
+
+ var img = new Image();
+ img.onload = function () {
+ curImage = img;
+ refresh();
+ };
+ img.src = '../test/qg.gif';
+ }
+ };
+
+ entry.init();
+
+ return entry;
+ }
+);
diff --git a/test/findBreakPoints.js b/test/findBreakPoints.js
new file mode 100644
index 0000000..5b6c08d
--- /dev/null
+++ b/test/findBreakPoints.js
@@ -0,0 +1,471 @@
+/**
+ * @file 查找轮廓中的关键点
+ * @author mengke01(kekee000@gmail.com)
+ *
+ * 查找方法为此处文章的方法
+ * http://wenku.baidu.com/link?url=Sz1BbYjfA7IeHAMVJ1TPlXiMhACc6sWhHliPJqAUxxtC5K9PYf4j3ile6patUEUMOE_9uJdsS1EGJBFLVbhXcn7Qe8l_FxuT3QVI8nm1aZG
+ *
+ * 关键点标记定义:
+ *
+ * p.left 左侧类型
+ * p.right: 右侧类型
+ * 直线为1,
+ * 曲线为2
+ */
+
+
+define(
+ function (require) {
+
+ var makeLink = require('graphics/pathUtil').makeLink;
+ var getCos = require('graphics/vector').getCos;
+ var getDist = require('graphics/vector').getDist;
+
+ var THETA_TANGENCY = 0.1; // 相切抑制
+ var THETA_CORNER = 0.5; // 拐点抑制
+ var THETA_MARK_COUNT = 0.8; // 标记点个数抑制
+ var THETA_INFLEXION_RANGE = 0.05; // 切线点之间的距离抑制
+
+
+ function isSegmentLine(contour, start, end, isLast) {
+ var contourSize = contour.length;
+ var mid;
+ var thetaDistance = 10; // 判断直线点距离
+
+ // 判断中间点距离
+ if (isLast) {
+ mid = contour[Math.floor(start.index + (contourSize - start.index + end.index) / 2) % contourSize];
+ }
+ else {
+ mid = contour[Math.floor((start.index + end.index) / 2)];
+ }
+
+ if (getDist(start, end, mid) > thetaDistance) {
+ return false;
+ }
+
+ // 随机选取 几个点进行直线判断
+ var startIndex = start.index;
+ var endIndex = end.index;
+ if (isLast) {
+ startIndex = start.index;
+ endIndex = contourSize + end.index;
+ }
+
+ // 距离比较长的话可以适当放大
+ if (endIndex - startIndex > 100) {
+ thetaDistance = 20;
+ }
+
+ var step = Math.floor(Math.max((endIndex - startIndex) / 10, 4));
+ var lineFlag = true;
+
+ for (var j = startIndex + step; j < endIndex; j += step) {
+ var dist = getDist(start, end, contour[j % contourSize]);
+ if (dist > thetaDistance) {
+ lineFlag = false;
+ break;
+ }
+ }
+
+ return lineFlag;
+ }
+
+
+ /**
+ * 计算当前点的theta值
+ *
+ * @param {Object} cur 当前点
+ * @param {Object} leftR 左侧平均
+ * @param {Object} rightR 右侧平均
+ * @return {number} theta值
+ */
+ function computeTheta(p, leftR, rightR) {
+ var leftX = 0;
+ var leftY = 0;
+ var t = 0;
+ var cur = p;
+ while (t++ < leftR) {
+ cur = cur.prev;
+ leftX += cur.x;
+ leftY += cur.y;
+ }
+ leftX = leftX / leftR;
+ leftY = leftY / leftR;
+
+ // 右侧分量
+ var rightX = 0;
+ var rightY = 0;
+ t = 0;
+ cur = p;
+ while (t++ < rightR) {
+ cur = cur.next;
+ rightX += cur.x;
+ rightY += cur.y;
+ }
+ rightX = rightX / rightR;
+ rightY = rightY / rightR;
+
+ leftX = p.x - leftX;
+ leftY = p.y - leftY;
+ rightX = rightX - p.x;
+ rightY = rightY - p.y;
+ var theta = Math.acos(getCos(leftX, leftY, rightX, rightY));
+ var m = leftX * rightY - rightX * leftY;
+ if (m > 0) {
+ return theta;
+ }
+ else if (m < 0) {
+ return -theta;
+ }
+
+ return 0;
+ }
+
+ /**
+ * 标记点的左右已经被访问过了
+ *
+ * @param {Object} p 点
+ * @param {number} rl 左半径
+ * @param {number} rr 右半径
+ */
+ function markVisited(p, rl, rr) {
+ var j;
+ var left = p;
+ var right = p;
+ p.visited = true;
+
+ if (!rr || rr === rl) {
+ j = 0;
+ while (j++ < rl) {
+ left = left.prev;
+ right = right.next;
+ left.visited = right.visited = true;
+ }
+ }
+ else {
+
+ j = 0;
+ while (j++ < rl) {
+ left = left.prev;
+ left.visited = true;
+ }
+
+ j = 0;
+ while (j++ < rr) {
+ right = right.next;
+ right.visited = true;
+ }
+ }
+ }
+
+ /**
+ * 查找和标记直线点
+ *
+ * @param {Array} contour 轮廓
+ * @param {Array} breakPoints 关键点
+ * @param {number} r 查找范围
+ * @return {Array} 关键点
+ */
+ function findLinePoints(contour, breakPoints, r) {
+
+ // 根据角点查找竖直和水平线
+ var linePoints = [];
+
+ var contourSize = contour.length;
+ for (var i = 0, l = breakPoints.length; i < l; i++) {
+ var isLast = i === l - 1;
+ var p = breakPoints[i];
+ var next = isLast ? breakPoints[0] : breakPoints[i + 1];
+
+ if (p.right === 1) {
+ continue;
+ }
+
+ var range = isLast ? contourSize - p.index + next.index : next.index - p.index;
+ if (range < r) {
+ p.right = 1;
+ next.left = 1;
+ }
+ else if (isSegmentLine(contour, p, next, isLast)){
+ p.right = 1;
+ next.left = 1;
+ }
+ }
+
+ return breakPoints;
+ }
+
+
+
+
+
+ /**
+ * 查找竖直和水平直线点
+ *
+ * @param {Array} contour 轮廓
+ * @param {Array} breakPoints 关键点
+ * @param {number} r 查找范围
+ * @return {Array} 关键点
+ */
+ function findVerticalAndHorizontalPoints(contour, breakPoints, r) {
+
+ var contourSize = contour.length;
+ for (var i = 0, l = contourSize; i < l; i++) {
+ var p = contour[i];
+ var cur;
+ var count;
+
+ // 查找竖直直线点
+ if (p.x === p.next.x && p.x === p.next.next.x) {
+ cur = p.next.next;
+ count = 2;
+
+ while (cur.x === cur.next.x) {
+ cur = cur.next;
+ count++;
+ }
+
+ if (count > 40) {
+
+ cur = cur.prev;
+ //p.right = 1;
+ if (-1 == breakPoints.indexOf(p)) {
+ breakPoints.push(p);
+ }
+
+ markVisited(p, r, count + r);
+ // 判断是否角点
+ p.theta = computeTheta(p, r, 1);
+ p.absTheta = Math.abs(p.theta);
+
+ if (p.absTheta > THETA_CORNER) {
+ p.breakPoint = true;
+ }
+ else if (p.absTheta <= THETA_TANGENCY) {
+ p.tangency = true;
+ }
+
+ if (count > 80) {
+ //cur.left = 1;
+ if (-1 == breakPoints.indexOf(cur)) {
+ breakPoints.push(cur);
+ }
+
+ cur.theta = computeTheta(cur, 1, r);
+ cur.absTheta = Math.abs(cur.theta);
+
+ if (cur.absTheta > THETA_CORNER) {
+ cur.breakPoint = true;
+ }
+ else if (cur.absTheta < THETA_TANGENCY) {
+ cur.tangency = true;
+ }
+ }
+ i += count;
+ }
+
+ }
+ else if (p.y === p.next.y && p.y === p.next.next.y) {
+ cur = p.next.next;
+ count = 2;
+
+ while (cur.y === cur.next.y) {
+ cur = cur.next;
+ count++;
+ }
+
+ if (count > 40) {
+
+ cur = cur.prev;
+
+ //p.right = 1;
+ if (-1 == breakPoints.indexOf(p)) {
+ breakPoints.push(p);
+ }
+ //cur.left = 1;
+ if (-1 == breakPoints.indexOf(cur)) {
+ breakPoints.push(cur);
+ }
+
+ markVisited(p, r, count + r);
+ // 判断是否角点
+ p.theta = computeTheta(p, r, 1);
+ p.absTheta = Math.abs(p.theta);
+
+ if (p.absTheta > THETA_CORNER) {
+ p.breakPoint = true;
+ }
+ else if (p.absTheta > THETA_TANGENCY) {
+ p.tangency = true;
+ }
+
+ cur.theta = computeTheta(cur, 1, r);
+ cur.absTheta = Math.abs(cur.theta);
+
+ if (cur.absTheta > THETA_CORNER) {
+ cur.breakPoint = true;
+ }
+ else if (cur.absTheta < THETA_TANGENCY) {
+ cur.tangency = true;
+ }
+
+ i += count;
+ }
+ }
+
+ }
+
+ breakPoints.sort(function (a, b) {
+ return a.index - b.index;
+ });
+
+ return breakPoints;
+ }
+
+
+
+ /**
+ * 查找和标记拐点
+ *
+ * @param {Array} contour 轮廓
+ * @param {Array} breakPoints 关键点
+ * @param {number} r 查找范围
+ * @return {Array} 关键点
+ */
+ function findBreakPoints(contour, breakPoints, r) {
+
+ var j;
+ var left;
+ var right;
+ var max;
+ contour.forEach(function (p) {
+ if (p.mark && !p.visited) {
+ j = 0;
+ left = p;
+ right = p;
+
+ max = p.absTheta;
+ while (j++ < r) {
+ left = left.prev;
+ right = right.next;
+
+ if (left.absTheta > max) {
+ max = left.absTheta;
+ }
+
+ if (right.absTheta > max) {
+ max = right.absTheta;
+ }
+ }
+
+ if (max === p.absTheta) {
+ p.breakPoint = true;
+ breakPoints.push(p);
+ markVisited(p, r);
+ }
+ }
+ });
+
+ return breakPoints;
+ }
+
+ function fixStartPoint(contour) {
+
+ var i;
+ var l;
+
+ var minY = contour[0].y;
+ var firstLine = [0];
+ for (i = 1, l = contour.length; i < l; i++) {
+ if (contour[i].y < minY) {
+ minY = contour[i].y;
+ firstLine = [i];
+ }
+ else if (contour[i].y === minY) {
+ firstLine.push(i);
+ }
+ }
+
+ var index = firstLine[0];
+ var minX = contour[index].x;
+ for (i = 1, l = firstLine.length; i < l; i++) {
+ if (contour[firstLine[i]].x < minX) {
+ minX = contour[firstLine[i]].x;
+ index = firstLine[i];
+ }
+ }
+
+ if (index !== 0) {
+ return contour.slice(index).concat(contour.slice(0, index));
+ }
+
+ return contour;
+ }
+
+
+ /**
+ * 查找轮廓中的关键点
+ *
+ * @param {Array} contour 轮廓点集合
+ * @return {Array} 轮廓点集合
+ */
+ function getBreakPoints(contour) {
+
+ //contour = fixStartPoint(contour);
+
+ contour = makeLink(contour);
+
+ var r = contour.length > 16 ? 10 : 4;
+
+ var contourSize = contour.length;
+ var breakPoints = [];
+
+ if (contour.length > 60) {
+ breakPoints = findVerticalAndHorizontalPoints(contour, breakPoints, r);
+ }
+
+
+ for (var i = 0, l = contourSize; i < l; i++) {
+ p = contour[i];
+ if (!p.visited) {
+ p.theta = computeTheta(p, r, r);
+ p.absTheta = Math.abs(p.theta);
+
+ if (p.absTheta > THETA_CORNER) {
+ p.mark = true;
+ }
+ else if (p.absTheta > THETA_TANGENCY) {
+ p.markTangency = true;
+ }
+ }
+ }
+
+ // 非极大抑制查找角点
+ breakPoints = findBreakPoints(contour, breakPoints, r);
+
+ // 没有找到拐点,说明拐点不明显,或者为连续曲线,这里采用4等分
+ if (breakPoints.length < 2) {
+
+ if (contourSize < 100) {
+ var step = Math.floor(contourSize / 2) + 1;
+ }
+ else {
+ var step = Math.floor(contourSize / 4) + 1;
+ }
+ var startIndex = breakPoints[0] ? breakPoints[0].index : 0;
+ for (var i = 0; i < contourSize; i += step) {
+ breakPoints.push(contour[(startIndex + i) % contourSize]);
+ }
+ }
+
+ breakPoints.sort(function (a, b) {
+ return a.index - b.index;
+ });
+
+ return breakPoints;
+ }
+
+ return getBreakPoints;
+ }
+);
diff --git a/test/qg.jpg b/test/qg.jpg
new file mode 100644
index 0000000..089bdfc
Binary files /dev/null and b/test/qg.jpg differ