Skip to content

Commit

Permalink
Merge branch 'master' of github.com:lifeart/sm-annotate
Browse files Browse the repository at this point in the history
  • Loading branch information
lifeart committed Feb 4, 2025
2 parents a012031 + 288ef1a commit 733238c
Show file tree
Hide file tree
Showing 22 changed files with 181 additions and 33 deletions.
15 changes: 8 additions & 7 deletions demo/index.js

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/types/core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ export declare class AnnotationTool extends AnnotationToolBase<IShape> {
initFrameCounter(): void;
init(videoElement: HTMLVideoElement | HTMLImageElement): void;
onKeyDown(event: KeyboardEvent): void;
removeLastShape(): void;
handleUndo(): void;
destroy(): void;
isCanvasInitialized: boolean;
Expand All @@ -89,6 +88,7 @@ export declare class AnnotationTool extends AnnotationToolBase<IShape> {
} | null;
_setCanvasSize(): boolean;
setCanvasSize(): void;
replaceShape(shape: IShape, index: number): void;
addShape(shape: IShape): void;
get msPerFrame(): number;
syncVideoSizes(): void;
Expand Down
1 change: 1 addition & 0 deletions dist/types/plugins/arrow.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export declare class ArrowToolPlugin extends BasePlugin<IArrow> implements ToolP
onPointerMove(event: PointerEvent): void;
onPointerUp(event: PointerEvent): void;
drawArrow(x1: number, y1: number, x2: number, y2: number): void;
isPointerAtShape(shape: IArrow, x: number, y: number): boolean;
}
2 changes: 2 additions & 0 deletions dist/types/plugins/base.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface ToolPlugin<T extends IShapeBase> {
onPointerDown: (event: PointerEvent) => void;
onPointerUp: (event: PointerEvent) => void;
onPointerMove: (event: PointerEvent) => void;
isPointerAtShape: (shape: T, x: number, y: number) => boolean;
onActivate: () => void;
onDeactivate: () => void;
reset: () => void;
Expand All @@ -26,6 +27,7 @@ export declare class BasePlugin<T extends IShapeBase> {
startY: number;
isDrawing: boolean;
constructor(annotationTool: AnnotationTool);
isPointerAtShape(_shape: T, _x: number, _y: number): boolean;
on(event: string, arg: unknown): void;
get ctx(): CanvasRenderingContext2D;
onDeactivate(): void;
Expand Down
1 change: 1 addition & 0 deletions dist/types/plugins/circle.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export declare class CircleToolPlugin extends BasePlugin<ICircle> implements Too
onPointerUp(event: PointerEvent): void;
drawCircle(x: number, y: number, radius: number): void;
draw(shape: ICircle): void;
isPointerAtShape(shape: ICircle, x: number, y: number): boolean;
}
1 change: 1 addition & 0 deletions dist/types/plugins/curve.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ export declare class CurveToolPlugin extends BasePlugin<ICurve> implements ToolP
onPointerUp(event: PointerEvent): void;
drawCurve(shape: Pick<ICurve, "points" | "lineWidth">): void;
initZoomCanvas(): void;
isPointerAtShape(shape: ICurve, x: number, y: number): boolean;
drawZoomCircle(x: number, y: number, isEnabled?: boolean): void;
}
1 change: 1 addition & 0 deletions dist/types/plugins/image.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export declare class ImageToolPlugin extends BasePlugin<IImage> implements ToolP
onPointerUp(event: PointerEvent): void;
normalize(shape: IImage, canvasWidth: number, canvasHeight: number): IImage;
draw(shape: IImage): void;
isPointerAtShape(shape: IImage, x: number, y: number): boolean;
}
1 change: 1 addition & 0 deletions dist/types/plugins/line.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export declare class LineToolPlugin extends BasePlugin<ILine> implements ToolPlu
onPointerUp(event: PointerEvent): void;
drawLine(x1: number, y1: number, x2: number, y2: number): void;
draw(shape: ILine): void;
isPointerAtShape(shape: ILine, x: number, y: number): boolean;
}
4 changes: 3 additions & 1 deletion dist/types/plugins/move.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ export interface IMove extends IShapeBase {
export declare class MoveToolPlugin extends BasePlugin<IMove> implements ToolPlugin<IMove> {
name: keyof ShapeMap;
shape: IShape | null;
shapeIndex: number;
lastDrawnShape: IShape | null;
shapeRemoved: boolean;
isScale: boolean;
move(shape: IMove): IMove;
normalize(shape: IMove): IMove;
onPointerDown(event: PointerEvent): void;
isPointerAtShape(shape: IShape, x: number, y: number): boolean;
isPointerAtCorner(rawShape: IImage | IAudioPeaks, x: number, y: number): boolean;
onPointerMove(event: PointerEvent): void;
onPointerUp(event: PointerEvent): void;
draw(): void;
reset(): void;
save(shape: IShape): void;
}
1 change: 1 addition & 0 deletions dist/types/plugins/rectangle.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export declare class RectangleToolPlugin extends BasePlugin<IRectangle> implemen
onPointerUp(event: PointerEvent): void;
drawRectangle(x: number, y: number, width: number, height: number): void;
draw(shape: IRectangle): void;
isPointerAtShape(shape: IRectangle, x: number, y: number): boolean;
}
1 change: 1 addition & 0 deletions dist/types/plugins/text.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ export declare class TextToolPlugin extends BasePlugin<IText> implements ToolPlu
private destroyPopup;
private createTextInputPopup;
onPointerUp(event: PointerEvent): void;
isPointerAtShape(shape: IText, x: number, y: number): boolean;
}
10 changes: 5 additions & 5 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,11 +343,6 @@ export class AnnotationTool extends AnnotationToolBase<IShape> {
}
}

removeLastShape() {
this.shapes.pop();
this.redrawFullCanvas();
}

handleUndo() {
if (this.undoStack.length > 0) {
this.shapes = this.undoStack.pop() as IShape[];
Expand Down Expand Up @@ -505,6 +500,11 @@ export class AnnotationTool extends AnnotationToolBase<IShape> {
}
}

replaceShape(shape: IShape, index: number) {
const serializedShape = this.serialize([shape])[0];
this.undoStack.push([...this.shapes]);
this.shapes[index] = serializedShape;
}
addShape(shape: IShape) {
const serializedShape = this.serialize([shape])[0];
this.undoStack.push([...this.shapes]);
Expand Down
15 changes: 15 additions & 0 deletions src/plugins/arrow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,19 @@ export class ArrowToolPlugin
);
this.ctx.stroke();
}
isPointerAtShape(shape: IArrow, x: number, y: number): boolean {
const { x1, y1, x2, y2 } = shape;
const tolerance = 5; // Adjust as needed

const distance = (x2 - x1) * (y1 - y) - (x1 - x) * (y2 - y1);
const lengthSquared = (x2 - x1) ** 2 + (y2 - y1) ** 2;

return (
Math.abs(distance) / Math.sqrt(lengthSquared) <= tolerance &&
x >= Math.min(x1, x2) - tolerance &&
x <= Math.max(x1, x2) + tolerance &&
y >= Math.min(y1, y2) - tolerance &&
y <= Math.max(y1, y2) + tolerance
);
}
}
4 changes: 4 additions & 0 deletions src/plugins/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface ToolPlugin<T extends IShapeBase> {
onPointerDown: (event: PointerEvent) => void;
onPointerUp: (event: PointerEvent) => void;
onPointerMove: (event: PointerEvent) => void;
isPointerAtShape: (shape: T, x: number, y: number) => boolean;
onActivate: () => void;
onDeactivate: () => void;
reset: () => void;
Expand All @@ -31,6 +32,9 @@ export class BasePlugin<T extends IShapeBase> {
constructor(annotationTool: AnnotationTool) {
this.annotationTool = annotationTool;
}
isPointerAtShape(_shape: T, _x: number, _y: number) {
return false;
}
on(event: string, arg: unknown) {
// noop
}
Expand Down
5 changes: 5 additions & 0 deletions src/plugins/circle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,9 @@ export class CircleToolPlugin
draw(shape: ICircle) {
this.drawCircle(shape.x, shape.y, shape.radius);
}
isPointerAtShape(shape: ICircle, x: number, y: number): boolean {
const dx = x - shape.x;
const dy = y - shape.y;
return dx * dx + dy * dy <= shape.radius * shape.radius;
}
}
45 changes: 45 additions & 0 deletions src/plugins/curve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,51 @@ export class CurveToolPlugin

// zoomCtx.scale(this.zoomScale, this.zoomScale);
}
isPointerAtShape(shape: ICurve, x: number, y: number) {
const threshold = this.ctx.lineWidth / 2;

for (let i = 0; i < shape.points.length - 1; i++) {
const point = shape.points[i];
const nextPoint = shape.points[i + 1];

// Calculate distances
const A = x - point.x;
const B = y - point.y;
const C = nextPoint.x - point.x;
const D = nextPoint.y - point.y;

const dot = A * C + B * D;
const lenSq = C * C + D * D;
let param = -1;

if (lenSq !== 0) {
param = dot / lenSq;
}

let xx, yy;

if (param < 0) {
xx = point.x;
yy = point.y;
} else if (param > 1) {
xx = nextPoint.x;
yy = nextPoint.y;
} else {
xx = point.x + param * C;
yy = point.y + param * D;
}

const dx = x - xx;
const dy = y - yy;
const distance = Math.sqrt(dx * dx + dy * dy);

if (distance < threshold) {
return true;
}
}

return false;
}

// Function to draw the zoomed circle centered on the current event coordinates without any visible offset
drawZoomCircle(x: number, y: number, isEnabled = false) {
Expand Down
8 changes: 8 additions & 0 deletions src/plugins/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,12 @@ export class ImageToolPlugin
shape.height
);
}
isPointerAtShape(shape: IImage, x: number, y: number): boolean {
return (
x >= shape.x &&
x <= shape.x + shape.width &&
y >= shape.y &&
y <= shape.y + shape.height
);
}
}
15 changes: 15 additions & 0 deletions src/plugins/line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,19 @@ export class LineToolPlugin
draw(shape: ILine) {
this.drawLine(shape.x1, shape.y1, shape.x2, shape.y2);
}
isPointerAtShape(shape: ILine, x: number, y: number): boolean {
const { x1, y1, x2, y2 } = shape;
const tolerance = 5; // Adjust as needed

const distance = (x2 - x1) * (y1 - y) - (x1 - x) * (y2 - y1);
const lengthSquared = (x2 - x1) ** 2 + (y2 - y1) ** 2;

return (
Math.abs(distance) / Math.sqrt(lengthSquared) <= tolerance &&
x >= Math.min(x1, x2) - tolerance &&
x <= Math.max(x1, x2) + tolerance &&
y >= Math.min(y1, y2) - tolerance &&
y <= Math.max(y1, y2) + tolerance
);
}
}
39 changes: 25 additions & 14 deletions src/plugins/move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export class MoveToolPlugin
{
name = "move" as keyof ShapeMap;
shape: IShape | null = null;
shapeIndex: number = -1;
lastDrawnShape: IShape | null = null;
shapeRemoved = false;
isScale = false;
move(shape: IMove) {
return shape;
Expand All @@ -27,19 +27,27 @@ export class MoveToolPlugin
}
onPointerDown(event: PointerEvent) {
const { x, y } = this.annotationTool.getRelativeCoords(event);
const lastShape = this.annotationTool.shapes.slice(0).pop();
if (!lastShape) {
const originalShapes = this.annotationTool.shapes;
const shapes = originalShapes.slice().reverse();
for (const shape of shapes) {
if (this.isPointerAtShape(shape, x, y)) {
this.shape = {...shape};
shape.fillStyle = 'rgba(0, 0, 0, 0)';
shape.strokeStyle = 'rgba(0, 0, 0, 0)';
this.shapeIndex = originalShapes.indexOf(shape);
break;
}
}
if (!this.shape) {
return;
}
this.shape = lastShape;
this.shapeRemoved = false;
this.lastDrawnShape = null;
this.startX = x;
this.startY = y;
this.isDrawing = true;
this.isScale =
lastShape.type === "image"
? this.isPointerAtCorner(lastShape, x, y)
this.shape.type === "image"
? this.isPointerAtCorner(this.shape, x, y)
: false;

if (this.isScale) {
Expand All @@ -49,6 +57,12 @@ export class MoveToolPlugin
}
}

isPointerAtShape(shape: IShape, x: number, y: number): boolean {
const deserializedShape = this.annotationTool.deserialize([shape])[0];
const plugin = this.annotationTool.pluginForTool(deserializedShape.type);
return plugin.isPointerAtShape(deserializedShape, x, y);
}

isPointerAtCorner(rawShape: IImage | IAudioPeaks, x: number, y: number) {
if (!('type' in rawShape)) {
return false;
Expand Down Expand Up @@ -88,11 +102,6 @@ export class MoveToolPlugin
return;
}

if (!this.shapeRemoved) {
this.annotationTool.removeLastShape();
this.shapeRemoved = true;
}

const { x, y } = this.annotationTool.getRelativeCoords(event);
const dx = x - this.startX;
const dy = y - this.startY;
Expand Down Expand Up @@ -159,7 +168,6 @@ export class MoveToolPlugin
this.isDrawing = false;
this.isScale = false;
this.shape = null;
this.shapeRemoved = false;
this.lastDrawnShape = null;
this.annotationTool.canvas.style.cursor = 'default';
}
Expand All @@ -171,7 +179,10 @@ export class MoveToolPlugin
this.shape = null;
this.isScale = false;
this.lastDrawnShape = null;
this.shapeRemoved = false;
this.shapeIndex = -1;
this.annotationTool.canvas.style.cursor = 'default';
}
save(shape: IShape) {
this.annotationTool.replaceShape(shape, this.shapeIndex);
}
}
19 changes: 19 additions & 0 deletions src/plugins/rectangle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,23 @@ export class RectangleToolPlugin
draw(shape: IRectangle) {
this.drawRectangle(shape.x, shape.y, shape.width, shape.height);
}
isPointerAtShape(shape: IRectangle, x: number, y: number): boolean {
const tolerance = 5;

// Check if point is near any of the rectangle edges
const nearLeftEdge = Math.abs(x - shape.x) <= tolerance;
const nearRightEdge = Math.abs(x - (shape.x + shape.width)) <= tolerance;
const nearTopEdge = Math.abs(y - shape.y) <= tolerance;
const nearBottomEdge = Math.abs(y - (shape.y + shape.height)) <= tolerance;

// Point must be within the vertical range of rectangle for left/right edges
const withinVerticalBounds = y >= shape.y && y <= shape.y + shape.height;
// Point must be within the horizontal range of rectangle for top/bottom edges
const withinHorizontalBounds = x >= shape.x && x <= shape.x + shape.width;

return (
(nearLeftEdge || nearRightEdge) && withinVerticalBounds ||
(nearTopEdge || nearBottomEdge) && withinHorizontalBounds
);
}
}
13 changes: 13 additions & 0 deletions src/plugins/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,17 @@ export class TextToolPlugin
const { x, y } = this.annotationTool.getRelativeCoords(event);
this.createTextInputPopup(x, y);
}

isPointerAtShape(shape: IText, x: number, y: number): boolean {
const lines = shape.text.split("\n");
const fontSize = 16 + this.ctx.lineWidth * 0.5;
const textHeight = lines.length * fontSize;
const textWidth = Math.max(...lines.map(line => this.ctx.measureText(line).width));
return (
x >= shape.x &&
x <= shape.x + textWidth &&
y >= shape.y - fontSize &&
y <= shape.y + textHeight - fontSize
);
}
}

0 comments on commit 733238c

Please sign in to comment.