Files
fonteditor/src/graphics/image/findBreakPoints.js
2015-03-25 19:31:34 +08:00

451 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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('../pathUtil').makeLink;
var getCos = require('../vector').getCos;
var THETA_TANGENCY = 0.1; // 相切抑制
var THETA_CORNER = 0.5; // 拐点抑制
var THETA_MARK_COUNT = 0.8; // 标记点个数抑制
var THETA_INFLEXION_RANGE = 0.05; // 切线点之间的距离抑制
function absCos(p0, p1, p) {
return Math.abs(getCos(p.x - p0.x, p.y - p0.y, p.x - p1.x, p.y - p1.y));
}
function isLine(p0, p1, p) {
return 1 - absCos(p0, p1, p) < 0.004;
}
function isPerfectLine(p0, p1, p) {
return 1 - absCos(p0, p1, p) < 0.002;
}
function removeOnLinePoint(points) {
// 移除直线上误判的点
for (var i = points.length - 1, last = i; i >= 0; i--) {
// 这里注意逆序
var p = points[i];
var next = i === last ? points[0] : points[i + 1];
var prev = i === 0 ? points[last] : points[i - 1];
if (p.inflexion && isPerfectLine(prev, next, p)) {
points.splice(i, 1);
last--;
continue;
}
}
return points;
}
function isSegmentLine(contour, start, end, isLast) {
var contourSize = contour.length;
// 随机选取 几个点进行直线判断
var startIndex = start.index;
var endIndex = end.index;
if (isLast) {
startIndex = start.index;
endIndex = contourSize + end.index;
}
var step = Math.floor(Math.max((endIndex - startIndex) / 10, 4));
var lineFlag = true;
for (var j = startIndex + step; j < endIndex; j += step) {
if (!isLine(start, end, contour[j % contourSize])) {
lineFlag = false;
break;
}
}
return lineFlag;
}
function removeInflexionPoint(points, contour) {
// 移除过多的切线点
var contourSize = contour.length;
var range;
for (var i = points.length - 1, last = i; i >= 0; i--) {
// 这里注意逆序
var p = points[i];
var next = i === last ? points[0] : points[i + 1];
var prev = i === 0 ? points[last] : points[i - 1];
// 两个同为切线点
if (p.inflexion && next.inflexion) {
range = i === last ? last + 1 - p.index + next.index : next.index - p.index;
if (range / contourSize < THETA_INFLEXION_RANGE) {
if (p.absTheta > next.absTheta) {
points.splice(i, 1);
}
else {
points.splice(i + 1, 1);
}
last--;
continue;
}
}
// 拐点附近的切线点
else if (p.breakPoint && next.inflexion) {
range = i === last ? last + 1 - p.index + next.index : next.index - p.index;
if (range / contourSize < THETA_INFLEXION_RANGE) {
points.splice(i + 1, 1);
i--;
last--;
continue;
}
}
// 拐点附近的切线点
else if (p.inflexion && next.breakPoint) {
range = i === last ? last + 1 - p.index + next.index : next.index - p.index;
if (range / contourSize < THETA_INFLEXION_RANGE) {
points.splice(i, 1);
i--;
last--;
continue;
}
}
}
return points;
}
/**
* 对轮廓中的关键点做进一步提取
*
* @param {Array} contour 轮廓点集
* @param {Array} breakPoints 关键点
* @param {number} r 搜索半径
*/
function filterBreakPoint(contour, breakPoints, r) {
var contourSize = contour.length;
breakPoints = removeOnLinePoint(breakPoints);
for (var i = 0, l = breakPoints.length; i < l; i++) {
var isLast = i === l - 1;
var start = breakPoints[i];
var end = breakPoints[isLast ? 0 : i + 1];
// 中间点小于10个,则判断为直线
var range = isLast ? contourSize - start.index + end.index : end.index - start.index;
if (start.breakPoint && end.breakPoint && range < 10) {
start.right = 1;
end.left = 1;
}
else {
// 判断中间点距离
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)];
}
// 夹角接近直线,这里需要对夹角接近直线的点进行修正,使直线点扩大或者缩小
var isOnLine = false;
if (isLine(start.next, end.prev, mid)) {
var isOnLine = true;
var from = start.next;
var to = end.prev;
while (absCos(from.prev, to, mid) >= absCos(from, to, mid)) {
if (from.prev !== start) {
from.prev.inflexion = start.inflexion;
from.prev.breakPoint = start.breakPoint;
delete start.inflexion;
delete start.breakPoint;
start = breakPoints[i] = from.prev;
}
from = from.prev;
}
while (absCos(from, to.next, mid) >= absCos(from, to, mid)) {
if (to.next !== to) {
to.next.inflexion = end.inflexion;
to.next.breakPoint = end.breakPoint;
delete end.inflexion;
delete end.breakPoint;
end = breakPoints[isLast ? 0 : i + 1] = to.next;
to = to.next;
}
}
}
if (isOnLine || isLine(start, end, mid)) {
if (isSegmentLine(contour, start, end, isLast)) {
start.right = 1;
end.left = 1;
}
else {
start.right = 2;
end.left = 2;
}
}
// 标记待选曲线
else {
start.right = 2;
end.left = 2;
}
}
}
if (breakPoints[0] === breakPoints[l - 1]) {
breakPoints.splice(l - 1, 1);
}
breakPoints = removeInflexionPoint(breakPoints, contour);
return breakPoints.sort(function (a, b) {
return a.index - b.index;
});
}
/**
* 计算当前点的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} r 半径
*/
function markVisited(p, r) {
var j = 0;
var left = p;
var right = p;
p.visited = true;
while (j++ < r) {
left = left.prev;
right = right.next;
left.visited = right.visited = true;
}
}
/**
* 查找轮廓中的关键点
*
* @param {Array} contour 轮廓点集合
* @return {Array} 轮廓点集合
*/
function findBreakPoints(contour) {
contour = makeLink(contour);
var r = contour.length > 10 ? 16 : 4;
var j = 0;
for (var i = 0, l = contour.length; i < l; i++) {
var p = contour[i];
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.tangencyMark = true;
}
}
// 非极大抑制查找角点
var breakPoints = [];
contour.forEach(function (p) {
if (p.mark && !p.visited) {
j = 0;
var left = p;
var right = p;
var 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);
}
}
});
var thetaMarkCount = THETA_MARK_COUNT * 2 * r;
var halfThetaMarkCount = 0.5 * thetaMarkCount;
contour.forEach(function (p) {
if (p.tangencyMark && !p.visited){
j = 0;
left = p;
right = p;
var leftMark = [0, 0, 0]; // 左侧标记数组,第一个为0的个数,第二个为-1的个数,第三个为1的个数
var rightMark = [0, 0, 0];
while (j++ < r) {
left = left.prev;
right = right.next;
var thetaL = computeTheta(left, r, j);
var thetaR = computeTheta(right, j, r);
if (Math.abs(thetaL) <= THETA_TANGENCY) {
leftMark[0]++;
}
else if (thetaL < THETA_TANGENCY) {
leftMark[1]++;
}
else if (thetaL > THETA_TANGENCY) {
leftMark[1]++;
}
if (Math.abs(thetaR) <= THETA_TANGENCY) {
rightMark[0]++;
}
else if (thetaR < THETA_TANGENCY) {
rightMark[1]++;
}
else if (thetaR > THETA_TANGENCY) {
rightMark[1]++;
}
}
// 检查两直线夹角比较小的情况
if (
leftMark[0] + rightMark[0] >= halfThetaMarkCount
) {
j = 0;
var left = p;
var right = p;
var 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.inflexion = true;
breakPoints.push(p);
markVisited(p, r);
}
}
// 检测直线曲线相切点
else if (
leftMark[2] + rightMark[0] >= thetaMarkCount
|| leftMark[0] + rightMark[2] >= thetaMarkCount
|| leftMark[1] + rightMark[0] >= thetaMarkCount
|| leftMark[0] + rightMark[1] >= thetaMarkCount
) {
p.inflexion = true;
breakPoints.push(p);
markVisited(p, r);
}
// 检查曲线相切点
else if (
leftMark[1] + rightMark[2] >= thetaMarkCount
|| leftMark[2] + rightMark[1] >= thetaMarkCount
) {
p.inflexion = true;
breakPoints.push(p);
markVisited(p, r);
}
}
});
breakPoints.sort(function (a, b) {
return a.index - b.index;
});
breakPoints = filterBreakPoint(contour, breakPoints, r);
return breakPoints;
}
return findBreakPoints;
}
);