Skip to content

Commit

Permalink
magnifier (#27)
Browse files Browse the repository at this point in the history
* magnifier

* +

* Update readme
  • Loading branch information
lifeart authored Nov 4, 2024
1 parent c9df2b1 commit 8aef8fa
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 11 deletions.
14 changes: 14 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ Users can draw and annotate using the available tools (curve, rectangle, ellipse

To save the current frame or all frames with annotations, use the `saveCurrentFrame` or `saveAllFrames` methods, respectively.


## Hotkeys

### Curve tool

| KeyCode | Result |
| - | - |
| `Shift` | Magnifier x2 |
| `r` | Red color |
| `g` | Green color |
| `b` | Blue color |
| `y` | Yellow color |
| `1` - `9` | Tool size |

## Contributing

We welcome contributions to improve the project. Please feel free to submit issues or pull requests for consideration.
Expand Down
14 changes: 13 additions & 1 deletion src/buttons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ export class ButtonConstructor {
}
};

this.addEvent(button, "click", onClick);
try {
this.tool.pluginForTool(tool);
this.addEvent(button, "click", onClick);
} catch (e) {
console.error(e);
button.disabled = true;
}

}

return button;
Expand Down Expand Up @@ -113,6 +120,11 @@ export function addButtons(tool: AnnotationTool, Button: ButtonConstructor) {
}
);

// Button.create(
// '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/></svg>',
// "zoom-draw"
// );

if (video) {
Button.create(
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="19 20 9 12 19 4 19 20"></polygon><line x1="5" y1="19" x2="5" y2="5"></line></svg>',
Expand Down
2 changes: 1 addition & 1 deletion src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ export class AnnotationTool extends AnnotationToolBase<IShape> {
super.destroy();
this.stopAnnotationsAsVideo();

this._currentTool = null;
this.currentTool = null;
this.plugins.forEach((plugin) => plugin.reset());
this.annotatedFrameCoordinates = [];
this.setFrameRate(DEFAULT_FPS);
Expand Down
129 changes: 120 additions & 9 deletions src/plugins/curve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,50 @@ export class CurveToolPlugin
{
name = "curve" as keyof ShapeMap;
curvePoints: IPoint[] = [];
zoomScale = 1.6; // Controls the magnification level
zoomRadius = 100; // Radius of the zoom circle
zoomCtx: CanvasRenderingContext2D | null = null; // Context for the zoom canvas
zoomCanvas: HTMLCanvasElement | null = null; // Zoom canvas element
move(shape: ICurve, dx: number, dy: number) {
shape.points = shape.points.map((point) => ({
x: point.x + dx,
y: point.y + dy,
}));
return shape;
}
colorMap = {
'r': '#d31a3b',
'g': '#15d33b',
'b': '#0085CA',
'y': '#F3CE32',
}
onKeyPress = (e: KeyboardEvent) => {
const key = e.key;
if (key === null || key === " " || e.isComposing) {
return;
}
const maybeNumeric = Number(key);
if (isNaN(maybeNumeric) || !maybeNumeric) {
if (key in this.colorMap) {
// @ts-expect-error
this.annotationTool.colorPicker.value = this.colorMap[key];
this.annotationTool.setCanvasSettings();
}
return;
}
this.annotationTool.strokeSizePicker.value = key;
this.annotationTool.setCanvasSettings();
};
onActivate() {
this.initZoomCanvas();
document.addEventListener("keypress", this.onKeyPress);
}
onDeactivate(): void {
this.zoomCtx = null;
this.zoomCanvas = null;
document.removeEventListener("keypress", this.onKeyPress);
}

normalize(shape: ICurve, canvasWidth: number, canvasHeight: number): ICurve {
return {
...shape,
Expand Down Expand Up @@ -54,34 +91,35 @@ export class CurveToolPlugin
this.curvePoints.push({ x, y });
}
onPointerMove(event: PointerEvent) {
const { x, y } = this.annotationTool.getRelativeCoords(event);
if (!this.isDrawing) {
this.drawZoomCircle(x, y, event.shiftKey); // Call the zoom functionality
return;
}
const { x, y } = this.annotationTool.getRelativeCoords(event);

this.curvePoints.push({ x, y });
this.drawCurve({
points: this.curvePoints,
lineWidth: this.ctx.lineWidth,
});
this.drawZoomCircle(x, y, event.shiftKey); // Call the zoom functionality
}

onPointerUp(event: PointerEvent) {
const { x, y } = this.annotationTool.getRelativeCoords(event);
this.drawZoomCircle(x, y, event.shiftKey);
if (!this.isDrawing) {
return;
}
const { x, y } = this.annotationTool.getRelativeCoords(event);

this.curvePoints.push({ x, y });

const curvePointsAsPoints = this.curvePoints.map(
(pt) => new Point(pt.x, pt.y)
);

// Optimize the points using the Douglas-Peucker algorithm
const epsilon = 2; // Adjust the epsilon value to control the level of simplification
const epsilon = 0.5; // Adjust the epsilon value to control the level of simplification
const optimizedPoints = douglasPeucker(curvePointsAsPoints, epsilon);

// Convert the optimized points back to the original point format
const optimizedCurvePoints = optimizedPoints.map((pt) => ({
x: pt.x,
y: pt.y,
Expand All @@ -95,12 +133,11 @@ export class CurveToolPlugin
lineWidth: this.ctx.lineWidth,
};
this.save(shape);
this.curvePoints = []; // Reset curve points
this.curvePoints = [];
this.isDrawing = false;
}
drawCurve(shape: Pick<ICurve, "points" | "lineWidth">) {
// if only two points and they are the same, draw a circle

drawCurve(shape: Pick<ICurve, "points" | "lineWidth">) {
if (
shape.points.length === 2 &&
shape.points[0].x === shape.points[1].x &&
Expand Down Expand Up @@ -135,4 +172,78 @@ export class CurveToolPlugin
this.ctx.stroke();
}
}

initZoomCanvas() {
const zoomCanvas = document.createElement("canvas");
const zoomResolutionMultiplier = 2; // Factor to increase resolution for sharpness

zoomCanvas.width = this.zoomRadius * 2 * zoomResolutionMultiplier; // diameter
zoomCanvas.height = this.zoomRadius * 2 * zoomResolutionMultiplier; // diameter
const zoomCtx = zoomCanvas.getContext("2d");
if (!zoomCtx) return;
zoomCtx.imageSmoothingQuality = "high"; // Improve sharpness
zoomCtx.imageSmoothingEnabled = true; // Improve sharpness
this.zoomCtx = zoomCtx;
this.zoomCanvas = zoomCanvas;
// zoomCtx.scale(this.zoomScale, this.zoomScale); // Increase resolution for sharpness

// zoomCtx.scale(this.zoomScale, this.zoomScale);
}

// Function to draw the zoomed circle centered on the current event coordinates without any visible offset
drawZoomCircle(x: number, y: number, isEnabled = false) {
if (!isEnabled) {
return;
}
if (!this.isDrawing) {
this.annotationTool.clearCanvas();
this.annotationTool.addVideoOverlay();
this.annotationTool.drawShapesOverlay();
}

// Create a temporary canvas for the zoom effect
const zoomCtx = this.zoomCtx;
if (!zoomCtx) return;

// Calculate the area to capture, centered exactly on (x, y) for zoom alignment
const captureWidth = this.zoomRadius * 2;
const captureHeight = this.zoomRadius * 2;
const captureX = 2 * x - captureWidth / 2;
const captureY = 2 * y - captureHeight / 2;

// console.log(captureX, captureY, captureWidth, captureHeight);
// Draw the magnified area from the main canvas onto the zoomCanvas
zoomCtx.drawImage(
this.ctx.canvas,
captureX, // Starting x coordinate on main canvas
captureY, // Starting y coordinate on main canvas
captureWidth, // Width of area to capture
captureHeight, // Height of area to capture
0, // Destination x on zoomCanvas
0, // Destination y on zoomCanvas
this.zoomRadius * 2, // Width on zoomCanvas (magnified)
this.zoomRadius * 2 // Height on zoomCanvas (magnified)
);

// this.ctx.drawImage(zoomCanvas, 0, 0);

// console.log(zoomCanvas.width, zoomCanvas.height);
// applySharpenFilter(zoomCtx, zoomCanvas.width, zoomCanvas.height);

// Draw the zoomCanvas content as a circular clipping region on the main canvas
this.ctx.save();
this.ctx.beginPath();
this.ctx.arc(x, y, this.zoomRadius, 0, 2 * Math.PI); // Define zoom circle centered on (x, y)
this.ctx.closePath();
this.ctx.clip();

// Draw the zoomed-in content centered exactly on (x, y)
this.ctx.drawImage(
this.zoomCanvas!,
x - this.zoomRadius,
y - this.zoomRadius
);

this.ctx.restore();
}
}

0 comments on commit 8aef8fa

Please sign in to comment.