contours.ts
Advanced tools
Comparing version 0.1.4 to 0.1.5
import { ImageDataLike } from './types/ImageDataLike'; | ||
import { Point } from './types/Point'; | ||
import { Circle, Rectangle } from './types/ShapeType'; | ||
import { Point, Polygon, ShapeCollection } from './types/ShapeType'; | ||
interface ContourFinderOptions { | ||
blur: boolean; | ||
threshold: number; | ||
blur?: boolean; | ||
threshold?: number; | ||
} | ||
@@ -12,3 +11,2 @@ /** | ||
export declare class ContourFinder { | ||
private static readonly THRESHOLD; | ||
private data; | ||
@@ -18,3 +16,3 @@ private readonly width; | ||
private readonly channels; | ||
readonly contours: Point[][]; | ||
readonly contours: Polygon[]; | ||
private isSimplified; | ||
@@ -72,4 +70,4 @@ /** | ||
*/ | ||
approximate(): Array<Rectangle | Circle | Point[]>; | ||
approximate(): ShapeCollection; | ||
} | ||
export {}; |
@@ -23,6 +23,9 @@ 'use strict'; | ||
var THRESHOLD = 6.25; // distance between two points | ||
function distance(p1, p2) { | ||
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); | ||
} | ||
} // perpendicular distance of a point from a line | ||
function perpendicularDistance(point, start, end) { | ||
@@ -34,3 +37,93 @@ if (start.x === end.x && start.y === end.y) { | ||
return Math.abs((start.y - end.y) * point.x + (end.x - start.x) * point.y + start.x * end.y - end.x * start.y) / distance(start, end); | ||
} // gets angle of a line | ||
function getLineAngle(p0, p1) { | ||
return Math.atan2(p1.y - p0.y, p1.x - p0.x); | ||
} // gets length of a line | ||
function getLineLength(p0, p1) { | ||
return Math.sqrt(Math.pow(p1.y - p0.y, 2) + Math.pow(p1.x - p0.x, 2)); | ||
} // angle between two lines | ||
function getLinesAngle(line1, line2) { | ||
var p0 = line1[0], | ||
p1 = line1[1]; | ||
var p2 = line2[0], | ||
p3 = line2[1]; | ||
var theta1 = getLineAngle(p0, p1); | ||
var theta2 = getLineAngle(p2, p3); // differenc of angle of two lines is angle between those lines | ||
return Math.abs(theta1 - theta2) * (180 / Math.PI); | ||
} // returns diffrnce in length between two lines | ||
function getLinesDifference(line1, line2) { | ||
var p0 = line1[0], | ||
p1 = line1[1]; | ||
var p2 = line2[0], | ||
p3 = line2[1]; | ||
var length1 = getLineLength(p0, p1); | ||
var length2 = getLineLength(p2, p3); | ||
return Math.abs(length1 - length2); | ||
} // checks if two opposite vertex have right:90 angle | ||
function isRectangle(contour) { | ||
var p0 = contour[0], | ||
p1 = contour[1], | ||
p2 = contour[2], | ||
p3 = contour[3]; | ||
var angle1 = getLinesAngle([p0, p1], [p1, p2]); | ||
var angle2 = getLinesAngle([p0, p3], [p3, p2]); | ||
var diff = Math.abs(angle2 - angle1); | ||
return diff <= THRESHOLD; | ||
} // check if two adjacent sides are same of same length | ||
function isSquare(contour) { | ||
var p0 = contour[0], | ||
p1 = contour[1], | ||
p2 = contour[2]; | ||
var edgeLength = getLineLength(p0, p1); | ||
var diff = getLinesDifference([p0, p1], [p1, p2]); | ||
return diff <= edgeLength * (THRESHOLD / 100); | ||
} // check if polygon is a circle | ||
function isCircle(contour) { | ||
// polygon has too few points | ||
if (contour.length < 16) return false; // find a circle equation from 3 distinct points | ||
var total = contour.length; | ||
var circle = circleFromThreePoints(contour[Math.floor(total * 0.25)], contour[Math.floor(total * 0.5)], contour[Math.floor(total * 0.75)]); // determine if all points in polygon fulfull this circle equation | ||
for (var i = 0; i < contour.length; i += 1) { | ||
var p = contour[i]; | ||
var r = Math.sqrt(Math.pow(p.x - circle.x, 2) + Math.pow(p.y - circle.y, 2)); | ||
if (circle.r - r > circle.r * THRESHOLD / 100) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} // gets a circle from 3 points | ||
function circleFromThreePoints(p0, p1, p2) { | ||
var x1 = p0.x; | ||
var y1 = p0.y; | ||
var x2 = p1.x; | ||
var y2 = p1.y; | ||
var x3 = p2.x; | ||
var y3 = p2.y; | ||
var a = x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2; | ||
var b = (x1 * x1 + y1 * y1) * (y3 - y2) + (x2 * x2 + y2 * y2) * (y1 - y3) + (x3 * x3 + y3 * y3) * (y2 - y1); | ||
var c = (x1 * x1 + y1 * y1) * (x2 - x3) + (x2 * x2 + y2 * y2) * (x3 - x1) + (x3 * x3 + y3 * y3) * (x1 - x2); | ||
var x = -b / (2 * a); | ||
var y = -c / (2 * a); | ||
return { | ||
x: x, | ||
y: y, | ||
r: Math.hypot(x - x1, y - y1) | ||
}; | ||
} | ||
/* | ||
@@ -91,7 +184,7 @@ | ||
for (var i = 1; i < endIndex; i += 1) { | ||
var _distance = perpendicularDistance(contour[i], contour[0], contour[endIndex]); | ||
var distance = perpendicularDistance(contour[i], contour[0], contour[endIndex]); | ||
if (_distance > maxDistance) { | ||
if (distance > maxDistance) { | ||
index = i; | ||
maxDistance = _distance; | ||
maxDistance = distance; | ||
} | ||
@@ -153,2 +246,4 @@ } | ||
function ContourFinder(imageData, options) { | ||
var _options$blur, _options$threshold; | ||
if (options === void 0) { | ||
@@ -171,9 +266,11 @@ options = { | ||
this.height = imageData.height; | ||
this.channels = this.data.length / (this.width * this.height); // preprocess image if multi channel | ||
this.channels = this.data.length / (this.width * this.height); | ||
var blur = (_options$blur = options.blur) != null ? _options$blur : false; | ||
var threshold = (_options$threshold = options.threshold) != null ? _options$threshold : 85; // preprocess image if multi channel | ||
if (this.channels > 1) { | ||
// blurs the image for better edge detection | ||
if (options.blur) this.blur(); // threshold image to get bit data | ||
if (blur) this.blur(); // threshold image to get bit data | ||
if (options.threshold > 0) this.toBitData(options.threshold); | ||
this.toBitData(threshold); | ||
} // perform contour detection | ||
@@ -212,3 +309,3 @@ | ||
var _loop3 = function _loop3(c) { | ||
_this.data[i + c] = Object.values(clockwiseOffset).reduce(function (prev, curr) { | ||
var avg = Object.values(clockwiseOffset).reduce(function (prev, curr) { | ||
prev += _this.data[_this.pointToIndex({ | ||
@@ -220,2 +317,3 @@ x: x + curr.x, | ||
}, _this.data[i + c]) / 9; | ||
_this.data[i + c] = avg; | ||
}; | ||
@@ -292,9 +390,9 @@ | ||
}; | ||
} else { | ||
return this.nextClockwise({ | ||
previous: nextPoint, | ||
boundary: boundary, | ||
start: start | ||
}); | ||
} | ||
return this.nextClockwise({ | ||
previous: nextPoint, | ||
boundary: boundary, | ||
start: start | ||
}); | ||
} | ||
@@ -436,5 +534,34 @@ /* | ||
_proto.approximate = function approximate() { | ||
if (!this.isSimplified) this.simplify(); // TODO: approximate contours | ||
var collection = { | ||
points: [], | ||
lines: [], | ||
triangles: [], | ||
squares: [], | ||
recangles: [], | ||
circles: [], | ||
polygons: [] | ||
}; | ||
if (!this.isSimplified) this.simplify(); | ||
this.contours.forEach(function (contour) { | ||
var length = contour.length; | ||
return []; | ||
if (length === 1) { | ||
collection.points.push(contour[0]); | ||
} else if (length === 2) { | ||
collection.lines.push(contour); | ||
} else if (length === 3) { | ||
collection.triangles.push(contour); | ||
} else if (length === 4 && isRectangle(contour)) { | ||
if (isSquare(contour)) { | ||
collection.squares.push(contour); | ||
} else { | ||
collection.recangles.push(contour); | ||
} | ||
} else if (isCircle(contour)) { | ||
collection.circles.push(contour); | ||
} else { | ||
collection.polygons.push(contour); | ||
} | ||
}); | ||
return collection; | ||
}; | ||
@@ -444,5 +571,4 @@ | ||
}(); | ||
ContourFinder.THRESHOLD = 85; | ||
exports.ContourFinder = ContourFinder; | ||
//# sourceMappingURL=contours.ts.cjs.development.js.map |
@@ -1,2 +0,2 @@ | ||
"use strict";function t(){return(t=Object.assign||function(t){for(var i=1;i<arguments.length;i++){var r=arguments[i];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(t[n]=r[n])}return t}).apply(this,arguments)}function i(t,i){return Math.sqrt(Math.pow(i.x-t.x,2)+Math.pow(i.y-t.y,2))}Object.defineProperty(exports,"__esModule",{value:!0});var r={"1,0":{x:1,y:1},"1,1":{x:0,y:1},"0,1":{x:-1,y:1},"-1,1":{x:-1,y:0},"-1,0":{x:-1,y:-1},"-1,-1":{x:0,y:-1},"0,-1":{x:1,y:-1},"1,-1":{x:1,y:0}},n=function(){function n(t,i){void 0===i&&(i={blur:!1,threshold:85}),this.contours=[],this.isSimplified=!1,this.visited={},this.data=t.data,this.width=t.width,this.height=t.height,this.channels=this.data.length/(this.width*this.height),this.channels>1&&(i.blur&&this.blur(),i.threshold>0&&this.toBitData(i.threshold)),this.extract()}var s=n.prototype;return s.pointToIndex=function(t){return t.y*this.width+t.x},s.blur=function(){for(var t=this,i=function(i){for(var n=function(n){for(var s=t.pointToIndex({x:i,y:n}),o=function(o){t.data[s+o]=Object.values(r).reduce((function(r,s){return r+(t.data[t.pointToIndex({x:i+s.x,y:n+s.y})]+o)}),t.data[s+o])/9},h=0;h<t.channels;h+=1)o(h)},s=0;s<t.height;s+=1)n(s)},n=0;n<this.width;n+=1)i(n)},s.toBitData=function(t){for(var i=[],r=0;r<this.data.length;r+=this.channels)i.push(.2126*this.data[r]+.7152*this.data[r+1]+.0722*this.data[r+2]>=t?255:0);this.data=i},s.nextClockwise=function(t){var i=t.previous,n=t.boundary,s=t.start,o=void 0===s?i:s,h=r[i.x-n.x+","+(i.y-n.y)],e={x:n.x+h.x,y:n.y+h.y};return e.x<0||e.y<0||e.x>=this.width||e.y>=this.height?this.nextClockwise({previous:e,boundary:n,start:o}):e.x===o.x&&e.y===o.y?{previous:e,boundary:n}:0===this.data[this.pointToIndex(e)]?{previous:i,boundary:e}:this.nextClockwise({previous:e,boundary:n,start:o})},s.traceContour=function(i){var r=[t({},i)],n={x:i.x,y:i.y+1},s=t({},i),o=t({},n);do{var h=this.nextClockwise({previous:o,boundary:s});o=h.previous;var e=this.pointToIndex(s=h.boundary);this.visited[e]||(r.push(s),this.visited[e]=!0)}while(o.x!==n.x||o.y!==n.y||s.x!==i.x||s.y!==i.y);return r},s.extract=function(){for(var t=!1,i=0;i<this.width;i+=1)for(var r=this.height-1;r>=0;r-=1){var n=this.pointToIndex({x:i,y:r});0===this.data[n]?this.visited[n]||t?t=!0:(this.visited[n]=!0,this.contours.push(this.traceContour({x:i,y:r}))):t=!1}},s.simplify=function(t){var r=this;return void 0===t&&(t=1),this.contours.forEach((function(n,s){r.contours[s]=function t(r,n){void 0===n&&(n=1);var s,o,h,e=r.length-1,a=0,u=0;if(r.length<=2)return r;for(var d=1;d<e;d+=1){var x=(s=r[d],(o=r[0]).x===(h=r[e]).x&&o.y===h.y?i(s,o):Math.abs((o.y-h.y)*s.x+(h.x-o.x)*s.y+o.x*h.y-h.x*o.y)/i(o,h));x>a&&(u=d,a=x)}return a>n?[].concat(t(r.slice(0,u),n),t(r.slice(u,e),n)):[r[0],r[e]]}(n,t)})),this.isSimplified=!0,this},s.approximate=function(){return this.isSimplified||this.simplify(),[]},n}();n.THRESHOLD=85,exports.ContourFinder=n; | ||
"use strict";function t(){return(t=Object.assign||function(t){for(var i=1;i<arguments.length;i++){var r=arguments[i];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(t[n]=r[n])}return t}).apply(this,arguments)}function i(t,i){return Math.sqrt(Math.pow(i.x-t.x,2)+Math.pow(i.y-t.y,2))}function r(t,r,n){return r.x===n.x&&r.y===n.y?i(t,r):Math.abs((r.y-n.y)*t.x+(n.x-r.x)*t.y+r.x*n.y-n.x*r.y)/i(r,n)}function n(t,i){return Math.atan2(i.y-t.y,i.x-t.x)}function s(t,i){return Math.sqrt(Math.pow(i.y-t.y,2)+Math.pow(i.x-t.x,2))}function o(t,i){var r=i[0],s=i[1],o=n(t[0],t[1]),a=n(r,s);return Math.abs(o-a)*(180/Math.PI)}Object.defineProperty(exports,"__esModule",{value:!0});var a={"1,0":{x:1,y:1},"1,1":{x:0,y:1},"0,1":{x:-1,y:1},"-1,1":{x:-1,y:0},"-1,0":{x:-1,y:-1},"-1,-1":{x:0,y:-1},"0,-1":{x:1,y:-1},"1,-1":{x:1,y:0}};exports.ContourFinder=function(){function i(t,i){var r,n;void 0===i&&(i={blur:!1,threshold:85}),this.contours=[],this.isSimplified=!1,this.visited={},this.data=t.data,this.width=t.width,this.height=t.height,this.channels=this.data.length/(this.width*this.height);var s=null!=(r=i.blur)&&r,o=null!=(n=i.threshold)?n:85;this.channels>1&&(s&&this.blur(),this.toBitData(o)),this.extract()}var n=i.prototype;return n.pointToIndex=function(t){return t.y*this.width+t.x},n.blur=function(){for(var t=this,i=function(i){for(var r=function(r){for(var n=t.pointToIndex({x:i,y:r}),s=function(s){var o=Object.values(a).reduce((function(n,o){return n+(t.data[t.pointToIndex({x:i+o.x,y:r+o.y})]+s)}),t.data[n+s])/9;t.data[n+s]=o},o=0;o<t.channels;o+=1)s(o)},n=0;n<t.height;n+=1)r(n)},r=0;r<this.width;r+=1)i(r)},n.toBitData=function(t){for(var i=[],r=0;r<this.data.length;r+=this.channels)i.push(.2126*this.data[r]+.7152*this.data[r+1]+.0722*this.data[r+2]>=t?255:0);this.data=i},n.nextClockwise=function(t){var i=t.previous,r=t.boundary,n=t.start,s=void 0===n?i:n,o=a[i.x-r.x+","+(i.y-r.y)],h={x:r.x+o.x,y:r.y+o.y};return h.x<0||h.y<0||h.x>=this.width||h.y>=this.height?this.nextClockwise({previous:h,boundary:r,start:s}):h.x===s.x&&h.y===s.y?{previous:h,boundary:r}:0===this.data[this.pointToIndex(h)]?{previous:i,boundary:h}:this.nextClockwise({previous:h,boundary:r,start:s})},n.traceContour=function(i){var r=[t({},i)],n={x:i.x,y:i.y+1},s=t({},i),o=t({},n);do{var a=this.nextClockwise({previous:o,boundary:s});o=a.previous;var h=this.pointToIndex(s=a.boundary);this.visited[h]||(r.push(s),this.visited[h]=!0)}while(o.x!==n.x||o.y!==n.y||s.x!==i.x||s.y!==i.y);return r},n.extract=function(){for(var t=!1,i=0;i<this.width;i+=1)for(var r=this.height-1;r>=0;r-=1){var n=this.pointToIndex({x:i,y:r});0===this.data[n]?this.visited[n]||t?t=!0:(this.visited[n]=!0,this.contours.push(this.traceContour({x:i,y:r}))):t=!1}},n.simplify=function(t){var i=this;return void 0===t&&(t=1),this.contours.forEach((function(n,s){i.contours[s]=function t(i,n){void 0===n&&(n=1);var s=i.length-1,o=0,a=0;if(i.length<=2)return i;for(var h=1;h<s;h+=1){var e=r(i[h],i[0],i[s]);e>o&&(a=h,o=e)}return o>n?[].concat(t(i.slice(0,a),n),t(i.slice(a,s),n)):[i[0],i[s]]}(n,t)})),this.isSimplified=!0,this},n.approximate=function(){var t={points:[],lines:[],triangles:[],squares:[],recangles:[],circles:[],polygons:[]};return this.isSimplified||this.simplify(),this.contours.forEach((function(i){var r=i.length;1===r?t.points.push(i[0]):2===r?t.lines.push(i):3===r?t.triangles.push(i):4===r&&function(t){var i=t[0],r=t[1],n=t[2],s=t[3],a=o([i,r],[r,n]),h=o([i,s],[s,n]);return Math.abs(h-a)<=6.25}(i)?function(t){var i=t[0],r=t[1],n=t[2],o=s(i,r);return function(t,i){var r=i[0],n=i[1],o=s(t[0],t[1]),a=s(r,n);return Math.abs(o-a)}([i,r],[r,n])<=.0625*o}(i)?t.squares.push(i):t.recangles.push(i):function(t){if(t.length<16)return!1;for(var i,r,n,s,o,a,h,e,u,y,c,x,l=t.length,d=(i=t[Math.floor(.25*l)],r=t[Math.floor(.5*l)],n=t[Math.floor(.75*l)],{x:c=-(((s=i.x)*s+(o=i.y)*o)*((u=n.y)-(h=r.y))+((a=r.x)*a+h*h)*(o-u)+((e=n.x)*e+u*u)*(h-o))/(2*(y=s*(h-u)-o*(a-e)+a*u-e*h)),y:x=-((s*s+o*o)*(a-e)+(a*a+h*h)*(e-s)+(e*e+u*u)*(s-a))/(2*y),r:Math.hypot(c-s,x-o)}),f=0;f<t.length;f+=1){var p=t[f],v=Math.sqrt(Math.pow(p.x-d.x,2)+Math.pow(p.y-d.y,2));if(d.r-v>6.25*d.r/100)return!1}return!0}(i)?t.circles.push(i):t.polygons.push(i)})),t},i}(); | ||
//# sourceMappingURL=contours.ts.cjs.production.min.js.map |
@@ -19,6 +19,9 @@ function _extends() { | ||
var THRESHOLD = 6.25; // distance between two points | ||
function distance(p1, p2) { | ||
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); | ||
} | ||
} // perpendicular distance of a point from a line | ||
function perpendicularDistance(point, start, end) { | ||
@@ -30,3 +33,93 @@ if (start.x === end.x && start.y === end.y) { | ||
return Math.abs((start.y - end.y) * point.x + (end.x - start.x) * point.y + start.x * end.y - end.x * start.y) / distance(start, end); | ||
} // gets angle of a line | ||
function getLineAngle(p0, p1) { | ||
return Math.atan2(p1.y - p0.y, p1.x - p0.x); | ||
} // gets length of a line | ||
function getLineLength(p0, p1) { | ||
return Math.sqrt(Math.pow(p1.y - p0.y, 2) + Math.pow(p1.x - p0.x, 2)); | ||
} // angle between two lines | ||
function getLinesAngle(line1, line2) { | ||
var p0 = line1[0], | ||
p1 = line1[1]; | ||
var p2 = line2[0], | ||
p3 = line2[1]; | ||
var theta1 = getLineAngle(p0, p1); | ||
var theta2 = getLineAngle(p2, p3); // differenc of angle of two lines is angle between those lines | ||
return Math.abs(theta1 - theta2) * (180 / Math.PI); | ||
} // returns diffrnce in length between two lines | ||
function getLinesDifference(line1, line2) { | ||
var p0 = line1[0], | ||
p1 = line1[1]; | ||
var p2 = line2[0], | ||
p3 = line2[1]; | ||
var length1 = getLineLength(p0, p1); | ||
var length2 = getLineLength(p2, p3); | ||
return Math.abs(length1 - length2); | ||
} // checks if two opposite vertex have right:90 angle | ||
function isRectangle(contour) { | ||
var p0 = contour[0], | ||
p1 = contour[1], | ||
p2 = contour[2], | ||
p3 = contour[3]; | ||
var angle1 = getLinesAngle([p0, p1], [p1, p2]); | ||
var angle2 = getLinesAngle([p0, p3], [p3, p2]); | ||
var diff = Math.abs(angle2 - angle1); | ||
return diff <= THRESHOLD; | ||
} // check if two adjacent sides are same of same length | ||
function isSquare(contour) { | ||
var p0 = contour[0], | ||
p1 = contour[1], | ||
p2 = contour[2]; | ||
var edgeLength = getLineLength(p0, p1); | ||
var diff = getLinesDifference([p0, p1], [p1, p2]); | ||
return diff <= edgeLength * (THRESHOLD / 100); | ||
} // check if polygon is a circle | ||
function isCircle(contour) { | ||
// polygon has too few points | ||
if (contour.length < 16) return false; // find a circle equation from 3 distinct points | ||
var total = contour.length; | ||
var circle = circleFromThreePoints(contour[Math.floor(total * 0.25)], contour[Math.floor(total * 0.5)], contour[Math.floor(total * 0.75)]); // determine if all points in polygon fulfull this circle equation | ||
for (var i = 0; i < contour.length; i += 1) { | ||
var p = contour[i]; | ||
var r = Math.sqrt(Math.pow(p.x - circle.x, 2) + Math.pow(p.y - circle.y, 2)); | ||
if (circle.r - r > circle.r * THRESHOLD / 100) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} // gets a circle from 3 points | ||
function circleFromThreePoints(p0, p1, p2) { | ||
var x1 = p0.x; | ||
var y1 = p0.y; | ||
var x2 = p1.x; | ||
var y2 = p1.y; | ||
var x3 = p2.x; | ||
var y3 = p2.y; | ||
var a = x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2; | ||
var b = (x1 * x1 + y1 * y1) * (y3 - y2) + (x2 * x2 + y2 * y2) * (y1 - y3) + (x3 * x3 + y3 * y3) * (y2 - y1); | ||
var c = (x1 * x1 + y1 * y1) * (x2 - x3) + (x2 * x2 + y2 * y2) * (x3 - x1) + (x3 * x3 + y3 * y3) * (x1 - x2); | ||
var x = -b / (2 * a); | ||
var y = -c / (2 * a); | ||
return { | ||
x: x, | ||
y: y, | ||
r: Math.hypot(x - x1, y - y1) | ||
}; | ||
} | ||
/* | ||
@@ -87,7 +180,7 @@ | ||
for (var i = 1; i < endIndex; i += 1) { | ||
var _distance = perpendicularDistance(contour[i], contour[0], contour[endIndex]); | ||
var distance = perpendicularDistance(contour[i], contour[0], contour[endIndex]); | ||
if (_distance > maxDistance) { | ||
if (distance > maxDistance) { | ||
index = i; | ||
maxDistance = _distance; | ||
maxDistance = distance; | ||
} | ||
@@ -149,2 +242,4 @@ } | ||
function ContourFinder(imageData, options) { | ||
var _options$blur, _options$threshold; | ||
if (options === void 0) { | ||
@@ -167,9 +262,11 @@ options = { | ||
this.height = imageData.height; | ||
this.channels = this.data.length / (this.width * this.height); // preprocess image if multi channel | ||
this.channels = this.data.length / (this.width * this.height); | ||
var blur = (_options$blur = options.blur) != null ? _options$blur : false; | ||
var threshold = (_options$threshold = options.threshold) != null ? _options$threshold : 85; // preprocess image if multi channel | ||
if (this.channels > 1) { | ||
// blurs the image for better edge detection | ||
if (options.blur) this.blur(); // threshold image to get bit data | ||
if (blur) this.blur(); // threshold image to get bit data | ||
if (options.threshold > 0) this.toBitData(options.threshold); | ||
this.toBitData(threshold); | ||
} // perform contour detection | ||
@@ -208,3 +305,3 @@ | ||
var _loop3 = function _loop3(c) { | ||
_this.data[i + c] = Object.values(clockwiseOffset).reduce(function (prev, curr) { | ||
var avg = Object.values(clockwiseOffset).reduce(function (prev, curr) { | ||
prev += _this.data[_this.pointToIndex({ | ||
@@ -216,2 +313,3 @@ x: x + curr.x, | ||
}, _this.data[i + c]) / 9; | ||
_this.data[i + c] = avg; | ||
}; | ||
@@ -288,9 +386,9 @@ | ||
}; | ||
} else { | ||
return this.nextClockwise({ | ||
previous: nextPoint, | ||
boundary: boundary, | ||
start: start | ||
}); | ||
} | ||
return this.nextClockwise({ | ||
previous: nextPoint, | ||
boundary: boundary, | ||
start: start | ||
}); | ||
} | ||
@@ -432,5 +530,34 @@ /* | ||
_proto.approximate = function approximate() { | ||
if (!this.isSimplified) this.simplify(); // TODO: approximate contours | ||
var collection = { | ||
points: [], | ||
lines: [], | ||
triangles: [], | ||
squares: [], | ||
recangles: [], | ||
circles: [], | ||
polygons: [] | ||
}; | ||
if (!this.isSimplified) this.simplify(); | ||
this.contours.forEach(function (contour) { | ||
var length = contour.length; | ||
return []; | ||
if (length === 1) { | ||
collection.points.push(contour[0]); | ||
} else if (length === 2) { | ||
collection.lines.push(contour); | ||
} else if (length === 3) { | ||
collection.triangles.push(contour); | ||
} else if (length === 4 && isRectangle(contour)) { | ||
if (isSquare(contour)) { | ||
collection.squares.push(contour); | ||
} else { | ||
collection.recangles.push(contour); | ||
} | ||
} else if (isCircle(contour)) { | ||
collection.circles.push(contour); | ||
} else { | ||
collection.polygons.push(contour); | ||
} | ||
}); | ||
return collection; | ||
}; | ||
@@ -440,5 +567,4 @@ | ||
}(); | ||
ContourFinder.THRESHOLD = 85; | ||
export { ContourFinder }; | ||
//# sourceMappingURL=contours.ts.esm.js.map |
@@ -1,3 +0,2 @@ | ||
import { Point } from './types/Point'; | ||
export declare function perpendicularDistance(point: Point, start: Point, end: Point): number; | ||
import { Polygon } from './types/ShapeType'; | ||
/** | ||
@@ -10,2 +9,2 @@ * | ||
*/ | ||
export declare function RDP(contour: Point[], epsilon?: number): Point[]; | ||
export declare function RDP(contour: Polygon, epsilon?: number): Polygon; |
@@ -1,15 +0,15 @@ | ||
export declare enum ShapeTypes { | ||
Rectangle = 0, | ||
Circle = 1 | ||
} | ||
export interface Rectangle { | ||
export interface Point { | ||
x: number; | ||
y: number; | ||
width: number; | ||
height: number; | ||
} | ||
export interface Circle { | ||
x: number; | ||
y: number; | ||
radius: number; | ||
export interface Polygon extends Array<Point> { | ||
} | ||
export interface ShapeCollection { | ||
points: Point[]; | ||
lines: Polygon[]; | ||
triangles: Polygon[]; | ||
squares: Polygon[]; | ||
recangles: Polygon[]; | ||
circles: Polygon[]; | ||
polygons: Polygon[]; | ||
} |
@@ -10,7 +10,8 @@ { | ||
"@types/jest": "^26.0.19", | ||
"@typescript-eslint/eslint-plugin": "^4.9.1", | ||
"@typescript-eslint/parser": "^4.9.1", | ||
"@types/prettier": "^2.1.6", | ||
"@typescript-eslint/eslint-plugin": "^4.11.0", | ||
"@typescript-eslint/parser": "^4.11.0", | ||
"babel-eslint": "^10.1.0", | ||
"eslint": "^7.15.0", | ||
"eslint-config-prettier": "^7.0.0", | ||
"eslint": "^7.16.0", | ||
"eslint-config-prettier": "^7.1.0", | ||
"eslint-config-react-app": "^6.0.0", | ||
@@ -24,3 +25,3 @@ "eslint-config-standard": "^16.0.2", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-prettier": "^3.2.0", | ||
"eslint-plugin-prettier": "^3.3.0", | ||
"eslint-plugin-promise": "^4.2.1", | ||
@@ -30,3 +31,3 @@ "eslint-plugin-react": "^7.21.5", | ||
"eslint-plugin-standard": "^4.1.0", | ||
"husky": "^4.3.5", | ||
"husky": "^4.3.6", | ||
"jest": "^26.6.3", | ||
@@ -65,8 +66,2 @@ "prettier": "^2.2.1", | ||
"peerDependencies": {}, | ||
"prettier": { | ||
"printWidth": 80, | ||
"semi": false, | ||
"singleQuote": true, | ||
"trailingComma": "es5" | ||
}, | ||
"repository": { | ||
@@ -97,3 +92,3 @@ "type": "git", | ||
"typings": "dist/index.d.ts", | ||
"version": "0.1.4" | ||
"version": "0.1.5" | ||
} |
@@ -21,3 +21,3 @@ # Contours.ts | ||
### Approximate Contours to Shapes | ||
### Approximate Contours to Common Shapes (Triangle, Rectangle, Square, Circle) | ||
@@ -73,4 +73,4 @@ ## How to use | ||
const { contours } = new ContourFinder(imageData, { | ||
blur: true // blurs the image data | ||
threshold: 85 // threshold image data | ||
blur: true, // blurs the image data | ||
threshold: 85, // threshold image data | ||
}) | ||
@@ -119,5 +119,16 @@ | ||
[ | ||
{x: 0, y: 0, width: 1, height: 1}, // Rect | ||
{x: 1, y: 1, radius: 1}, // Circle | ||
[{x: 0, y: 0}, {x: 3, y: 3}] // Polygon | ||
{ | ||
points: [[{x: 0, y: 0}]], | ||
lines: [[{x: 0, y: 0}, {x: 3, y: 3}]], | ||
triangles: [], | ||
squares: [ | ||
[{ x: 0, y: 0 }, | ||
{ x: 5, y: 0 }, | ||
{ x: 5, y: 5 }, | ||
{ x: 0, y: 5 },] | ||
], | ||
recangles: [], | ||
circles: [], | ||
polygons: [], | ||
} | ||
] | ||
@@ -124,0 +135,0 @@ */ |
import { RDP } from './rdp' | ||
import { ImageDataLike } from './types/ImageDataLike' | ||
import { Point } from './types/Point' | ||
import { Circle, Rectangle } from './types/ShapeType' | ||
import { Point, Polygon, ShapeCollection } from './types/ShapeType' | ||
import { isCircle, isRectangle, isSquare } from './utilities' | ||
@@ -26,4 +26,4 @@ /** | ||
interface ContourFinderOptions { | ||
blur: boolean | ||
threshold: number | ||
blur?: boolean | ||
threshold?: number | ||
} | ||
@@ -35,4 +35,2 @@ | ||
export class ContourFinder { | ||
private static readonly THRESHOLD = 85 | ||
private data: Uint8ClampedArray | number[] | ||
@@ -43,3 +41,3 @@ private readonly width: number | ||
public readonly contours: Point[][] = [] | ||
public readonly contours: Polygon[] = [] | ||
@@ -65,9 +63,12 @@ private isSimplified = false | ||
const blur = options.blur ?? false | ||
const threshold = options.threshold ?? 85 | ||
// preprocess image if multi channel | ||
if (this.channels > 1) { | ||
// blurs the image for better edge detection | ||
if (options.blur) this.blur() | ||
if (blur) this.blur() | ||
// threshold image to get bit data | ||
if (options.threshold > 0) this.toBitData(options.threshold) | ||
this.toBitData(threshold) | ||
} | ||
@@ -100,3 +101,3 @@ | ||
for (let c = 0; c < this.channels; c += 1) { | ||
this.data[i + c] = | ||
const avg = | ||
Object.values(clockwiseOffset).reduce((prev, curr) => { | ||
@@ -113,2 +114,4 @@ prev += | ||
}, this.data[i + c]) / 9 | ||
this.data[i + c] = avg | ||
} | ||
@@ -190,5 +193,5 @@ } | ||
} | ||
} else { | ||
return this.nextClockwise({ previous: nextPoint, boundary, start }) | ||
} | ||
return this.nextClockwise({ previous: nextPoint, boundary, start }) | ||
} | ||
@@ -236,4 +239,4 @@ | ||
*/ | ||
private traceContour(first: Point): Point[] { | ||
const contour: Point[] = [{ ...first }] | ||
private traceContour(first: Point): Polygon { | ||
const contour: Polygon = [{ ...first }] | ||
// the point we entered first from | ||
@@ -331,8 +334,39 @@ const firstPrevious = { | ||
*/ | ||
public approximate(): Array<Rectangle | Circle | Point[]> { | ||
public approximate(): ShapeCollection { | ||
const collection: ShapeCollection = { | ||
points: [], | ||
lines: [], | ||
triangles: [], | ||
squares: [], | ||
recangles: [], | ||
circles: [], | ||
polygons: [], | ||
} | ||
if (!this.isSimplified) this.simplify() | ||
// TODO: approximate contours | ||
return [] | ||
this.contours.forEach((contour) => { | ||
const { length } = contour | ||
if (length === 1) { | ||
collection.points.push(contour[0]) | ||
} else if (length === 2) { | ||
collection.lines.push(contour) | ||
} else if (length === 3) { | ||
collection.triangles.push(contour) | ||
} else if (length === 4 && isRectangle(contour)) { | ||
if (isSquare(contour)) { | ||
collection.squares.push(contour) | ||
} else { | ||
collection.recangles.push(contour) | ||
} | ||
} else if (isCircle(contour)) { | ||
collection.circles.push(contour) | ||
} else { | ||
collection.polygons.push(contour) | ||
} | ||
}) | ||
return collection | ||
} | ||
} |
@@ -1,26 +0,4 @@ | ||
import { Point } from './types/Point' | ||
import { Polygon } from './types/ShapeType' | ||
import { perpendicularDistance } from './utilities' | ||
function distance(p1: Point, p2: Point): number { | ||
return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2) | ||
} | ||
export function perpendicularDistance( | ||
point: Point, | ||
start: Point, | ||
end: Point | ||
): number { | ||
if (start.x === end.x && start.y === end.y) { | ||
return distance(point, start) | ||
} | ||
return ( | ||
Math.abs( | ||
(start.y - end.y) * point.x + | ||
(end.x - start.x) * point.y + | ||
start.x * end.y - | ||
end.x * start.y | ||
) / distance(start, end) | ||
) | ||
} | ||
/* | ||
@@ -67,5 +45,5 @@ | ||
*/ | ||
export function RDP(contour: Point[], epsilon = 1): Point[] { | ||
export function RDP(contour: Polygon, epsilon = 1): Polygon { | ||
const endIndex = contour.length - 1 | ||
let collection: Point[] = [] | ||
let collection: Polygon = [] | ||
let maxDistance = 0 | ||
@@ -72,0 +50,0 @@ let index = 0 |
@@ -1,17 +0,16 @@ | ||
export enum ShapeTypes { | ||
Rectangle, | ||
Circle, | ||
} | ||
export interface Rectangle { | ||
export interface Point { | ||
x: number | ||
y: number | ||
width: number | ||
height: number | ||
} | ||
export interface Circle { | ||
x: number | ||
y: number | ||
radius: number | ||
export interface Polygon extends Array<Point> {} | ||
export interface ShapeCollection { | ||
points: Point[] | ||
lines: Polygon[] | ||
triangles: Polygon[] | ||
squares: Polygon[] | ||
recangles: Polygon[] | ||
circles: Polygon[] | ||
polygons: Polygon[] | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
140071
1581
173
31