mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Remove aspect ratio
This commit is contained in:
parent
89a733120f
commit
1919b3db1a
1 changed files with 34 additions and 38 deletions
|
@ -28,19 +28,21 @@ interface ShapeRecognitionResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ShapeRecognitionOptions {
|
interface ShapeRecognitionOptions {
|
||||||
closedDistThreshPercent: number; // Max distance between stroke start/end to consider shape closed
|
shapeIsClosedPercentThreshold: number; // Max distance between stroke start/end to consider shape closed
|
||||||
cornerAngleThresh: number; // Angle (in degrees) below which a corner is considered "sharp" (for arrow detection)
|
arrowTipAngleThreshold: number; // Angle (in degrees) below which a corner is considered "sharp" (for arrow detection)
|
||||||
rdpTolerancePercent: number; // RDP simplification tolerance (percentage of bounding box diagonal)
|
rdpTolerancePercent: number; // RDP simplification tolerance (percentage of bounding box diagonal)
|
||||||
rectAngleThresh: number; // Angle (in degrees) to check for rectangle corners
|
rectangleCornersAngleThreshold: number; // Angle (in degrees) to check for rectangle corners
|
||||||
rectOrientationThresh: number; // Angle difference (in degrees) to nearest 0/90 orientation to call it rectangle
|
rectangleOrientationAngleThreshold: number; // Angle difference (in degrees) to nearest 0/90 orientation to call it rectangle
|
||||||
|
ellipseRadiusVarianceThreshold: number; // Variance in radius to consider a shape an ellipse
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_OPTIONS: ShapeRecognitionOptions = {
|
const DEFAULT_OPTIONS: ShapeRecognitionOptions = {
|
||||||
closedDistThreshPercent: 10, // distance between start/end < % of bounding box diagonal
|
shapeIsClosedPercentThreshold: 20,
|
||||||
cornerAngleThresh: 60, // <60° considered a sharp corner (possible arrow tip)
|
arrowTipAngleThreshold: 60,
|
||||||
rdpTolerancePercent: 10, // percentage of bounding box diagonal
|
rdpTolerancePercent: 10,
|
||||||
rectAngleThresh: 20, // <20° considered a sharp corner (rectangle)
|
rectangleCornersAngleThreshold: 20,
|
||||||
rectOrientationThresh: 10, //
|
rectangleOrientationAngleThreshold: 10,
|
||||||
|
ellipseRadiusVarianceThreshold: 0.5
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,16 +69,14 @@ export const recognizeShape = (
|
||||||
[boundingBox.maxX, boundingBox.maxY] as LocalPoint,
|
[boundingBox.maxX, boundingBox.maxY] as LocalPoint,
|
||||||
) * options.rdpTolerancePercent / 100;
|
) * options.rdpTolerancePercent / 100;
|
||||||
const simplified = simplifyRDP(element.points, tolerance);
|
const simplified = simplifyRDP(element.points, tolerance);
|
||||||
console.log("Simplified points:", simplified);
|
|
||||||
|
|
||||||
// Check if the original points form a closed shape
|
|
||||||
const start = element.points[0], end = element.points[element.points.length - 1];
|
const start = element.points[0], end = element.points[element.points.length - 1];
|
||||||
const closedDist = pointDistance(start, end);
|
const closedDist = pointDistance(start, end);
|
||||||
const diag = Math.hypot(boundingBox.width, boundingBox.height); // diagonal of bounding box
|
const boundingBoxDiagonal = Math.hypot(boundingBox.width, boundingBox.height);
|
||||||
const isClosed = closedDist < Math.max(10, diag * options.closedDistThreshPercent / 100); // e.g., threshold: 10px or % of size
|
// e.g., threshold: 10px or % of size
|
||||||
console.log("Closed shape:", isClosed);
|
const isClosed = closedDist < Math.max(10, boundingBoxDiagonal * options.shapeIsClosedPercentThreshold / 100);
|
||||||
|
|
||||||
let bestShape: Shape = 'freedraw'; // TODO: Should this even be possible in this mode?
|
let bestShape: Shape = 'freedraw';
|
||||||
|
|
||||||
const boundingBoxCenter = getCenterForBounds([
|
const boundingBoxCenter = getCenterForBounds([
|
||||||
boundingBox.minX,
|
boundingBox.minX,
|
||||||
|
@ -127,7 +127,7 @@ export const recognizeShape = (
|
||||||
console.log("Angles sum:", angles.reduce((a, b) => a + b, 0));
|
console.log("Angles sum:", angles.reduce((a, b) => a + b, 0));
|
||||||
|
|
||||||
// All angles are sharp enough, so we can check for rectangle/diamond
|
// All angles are sharp enough, so we can check for rectangle/diamond
|
||||||
if (angles.every(a => (a > options.rectAngleThresh && a < 180 - options.rectAngleThresh))) {
|
if (angles.every(a => (a > options.rectangleCornersAngleThreshold && a < 180 - options.rectangleCornersAngleThreshold))) {
|
||||||
// Determine orientation by checking the slope of each segment
|
// Determine orientation by checking the slope of each segment
|
||||||
interface Segment { length: number; angleDeg: number; }
|
interface Segment { length: number; angleDeg: number; }
|
||||||
const segments: Segment[] = [];
|
const segments: Segment[] = [];
|
||||||
|
@ -148,7 +148,7 @@ export const recognizeShape = (
|
||||||
const angle = seg.angleDeg;
|
const angle = seg.angleDeg;
|
||||||
const distToHoriz = Math.min(Math.abs(angle - 0), Math.abs(angle - 180));
|
const distToHoriz = Math.min(Math.abs(angle - 0), Math.abs(angle - 180));
|
||||||
const distToVert = Math.abs(angle - 90);
|
const distToVert = Math.abs(angle - 90);
|
||||||
return (distToHoriz < options.rectOrientationThresh) || (distToVert < options.rectOrientationThresh);
|
return (distToHoriz < options.rectangleOrientationAngleThreshold) || (distToVert < options.rectangleOrientationAngleThreshold);
|
||||||
});
|
});
|
||||||
if (hasAxisAlignedSide) {
|
if (hasAxisAlignedSide) {
|
||||||
bestShape = "rectangle";
|
bestShape = "rectangle";
|
||||||
|
@ -157,26 +157,22 @@ export const recognizeShape = (
|
||||||
bestShape = "diamond";
|
bestShape = "diamond";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (isClosed) { // **Ellipse** (closed shape with few corners)
|
||||||
const aspectRatio = boundingBox.width && boundingBox.height ? Math.min(boundingBox.width, boundingBox.height) / Math.max(boundingBox.width, boundingBox.height) : 1;
|
// Measure radius variance
|
||||||
// If aspect ratio ~1 (nearly square) and simplified has few corners, good for circle
|
const cx = boundingBoxCenter[0];
|
||||||
if (aspectRatio > 0.8) {
|
const cy = boundingBoxCenter[1];
|
||||||
// Measure radius variance
|
let totalDist = 0, maxDist = 0, minDist = Infinity;
|
||||||
const cx = boundingBoxCenter[0];
|
for (const p of simplified) {
|
||||||
const cy = boundingBoxCenter[1];
|
const d = Math.hypot(p[0] - cx, p[1] - cy);
|
||||||
let totalDist = 0, maxDist = 0, minDist = Infinity;
|
totalDist += d;
|
||||||
for (const p of simplified) {
|
maxDist = Math.max(maxDist, d);
|
||||||
const d = Math.hypot(p[0] - cx, p[1] - cy);
|
minDist = Math.min(minDist, d);
|
||||||
totalDist += d;
|
}
|
||||||
maxDist = Math.max(maxDist, d);
|
const avgDist = totalDist / simplified.length;
|
||||||
minDist = Math.min(minDist, d);
|
const radiusVar = (maxDist - minDist) / (avgDist || 1);
|
||||||
}
|
// If variance in radius is small, shape is round
|
||||||
const avgDist = totalDist / simplified.length;
|
if (radiusVar < options.ellipseRadiusVarianceThreshold) {
|
||||||
const radiusVar = (maxDist - minDist) / (avgDist || 1);
|
bestShape = 'ellipse';
|
||||||
// If variance in radius is small, shape is round
|
|
||||||
if (radiusVar < 0.3) {
|
|
||||||
bestShape = 'ellipse';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue