fix: resolve crop tool visual feedback issue
Simplified the crop tool overlay rendering to fix the issue where the crop area was not visible during dragging. Changes: - Removed overlayCanvas property and complex image preservation logic - Added initialized flag for lazy initialization on first use - Rewrote drawCropOverlay to use ctx.save()/restore() pattern - Draw overlay elements directly on canvas context (darkened areas, border, rule of thirds grid, and resize handles) The crop tool now properly displays the crop rectangle, handles, and overlay during all interactions (defining, dragging, and resizing). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -11,41 +11,28 @@ export class CropTool extends BaseTool {
|
||||
private activeHandle: string | null = null;
|
||||
private startX = 0;
|
||||
private startY = 0;
|
||||
private overlayCanvas: HTMLCanvasElement | null = null;
|
||||
private handleSize = 8;
|
||||
private initialized = false;
|
||||
|
||||
constructor() {
|
||||
super('Crop');
|
||||
}
|
||||
|
||||
onActivate(): void {
|
||||
// Initialize crop rect to full canvas size when activated
|
||||
if (this.overlayCanvas) {
|
||||
this.cropRect = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: this.overlayCanvas.width,
|
||||
height: this.overlayCanvas.height,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
onPointerDown(
|
||||
pointer: PointerState,
|
||||
ctx: CanvasRenderingContext2D,
|
||||
settings: ToolSettings
|
||||
): void {
|
||||
// Create overlay canvas if not exists
|
||||
if (!this.overlayCanvas) {
|
||||
this.overlayCanvas = document.createElement('canvas');
|
||||
this.overlayCanvas.width = ctx.canvas.width;
|
||||
this.overlayCanvas.height = ctx.canvas.height;
|
||||
// Initialize crop rect to full canvas on first use
|
||||
if (!this.initialized) {
|
||||
this.cropRect = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: ctx.canvas.width,
|
||||
height: ctx.canvas.height,
|
||||
};
|
||||
this.initialized = true;
|
||||
this.drawCropOverlay(ctx);
|
||||
}
|
||||
|
||||
// Check if clicking on a resize handle
|
||||
@@ -236,30 +223,25 @@ export class CropTool extends BaseTool {
|
||||
}
|
||||
|
||||
private drawCropOverlay(ctx: CanvasRenderingContext2D): void {
|
||||
if (!this.overlayCanvas) return;
|
||||
|
||||
const overlayCtx = this.overlayCanvas.getContext('2d');
|
||||
if (!overlayCtx) return;
|
||||
|
||||
// Clear overlay
|
||||
overlayCtx.clearRect(0, 0, this.overlayCanvas.width, this.overlayCanvas.height);
|
||||
// Save context state
|
||||
ctx.save();
|
||||
|
||||
// Draw darkened areas outside crop rect
|
||||
overlayCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
||||
|
||||
// Top
|
||||
overlayCtx.fillRect(0, 0, this.overlayCanvas.width, this.cropRect.y);
|
||||
ctx.fillRect(0, 0, ctx.canvas.width, this.cropRect.y);
|
||||
|
||||
// Bottom
|
||||
overlayCtx.fillRect(
|
||||
ctx.fillRect(
|
||||
0,
|
||||
this.cropRect.y + this.cropRect.height,
|
||||
this.overlayCanvas.width,
|
||||
this.overlayCanvas.height - (this.cropRect.y + this.cropRect.height)
|
||||
ctx.canvas.width,
|
||||
ctx.canvas.height - (this.cropRect.y + this.cropRect.height)
|
||||
);
|
||||
|
||||
// Left
|
||||
overlayCtx.fillRect(
|
||||
ctx.fillRect(
|
||||
0,
|
||||
this.cropRect.y,
|
||||
this.cropRect.x,
|
||||
@@ -267,17 +249,17 @@ export class CropTool extends BaseTool {
|
||||
);
|
||||
|
||||
// Right
|
||||
overlayCtx.fillRect(
|
||||
ctx.fillRect(
|
||||
this.cropRect.x + this.cropRect.width,
|
||||
this.cropRect.y,
|
||||
this.overlayCanvas.width - (this.cropRect.x + this.cropRect.width),
|
||||
ctx.canvas.width - (this.cropRect.x + this.cropRect.width),
|
||||
this.cropRect.height
|
||||
);
|
||||
|
||||
// Draw crop rect border
|
||||
overlayCtx.strokeStyle = '#ffffff';
|
||||
overlayCtx.lineWidth = 2;
|
||||
overlayCtx.strokeRect(
|
||||
ctx.strokeStyle = '#ffffff';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(
|
||||
this.cropRect.x,
|
||||
this.cropRect.y,
|
||||
this.cropRect.width,
|
||||
@@ -285,39 +267,39 @@ export class CropTool extends BaseTool {
|
||||
);
|
||||
|
||||
// Draw rule of thirds grid
|
||||
overlayCtx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
|
||||
overlayCtx.lineWidth = 1;
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
// Vertical lines
|
||||
overlayCtx.beginPath();
|
||||
overlayCtx.moveTo(this.cropRect.x + this.cropRect.width / 3, this.cropRect.y);
|
||||
overlayCtx.lineTo(this.cropRect.x + this.cropRect.width / 3, this.cropRect.y + this.cropRect.height);
|
||||
overlayCtx.moveTo(this.cropRect.x + (this.cropRect.width * 2) / 3, this.cropRect.y);
|
||||
overlayCtx.lineTo(this.cropRect.x + (this.cropRect.width * 2) / 3, this.cropRect.y + this.cropRect.height);
|
||||
overlayCtx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.cropRect.x + this.cropRect.width / 3, this.cropRect.y);
|
||||
ctx.lineTo(this.cropRect.x + this.cropRect.width / 3, this.cropRect.y + this.cropRect.height);
|
||||
ctx.moveTo(this.cropRect.x + (this.cropRect.width * 2) / 3, this.cropRect.y);
|
||||
ctx.lineTo(this.cropRect.x + (this.cropRect.width * 2) / 3, this.cropRect.y + this.cropRect.height);
|
||||
ctx.stroke();
|
||||
|
||||
// Horizontal lines
|
||||
overlayCtx.beginPath();
|
||||
overlayCtx.moveTo(this.cropRect.x, this.cropRect.y + this.cropRect.height / 3);
|
||||
overlayCtx.lineTo(this.cropRect.x + this.cropRect.width, this.cropRect.y + this.cropRect.height / 3);
|
||||
overlayCtx.moveTo(this.cropRect.x, this.cropRect.y + (this.cropRect.height * 2) / 3);
|
||||
overlayCtx.lineTo(this.cropRect.x + this.cropRect.width, this.cropRect.y + (this.cropRect.height * 2) / 3);
|
||||
overlayCtx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.cropRect.x, this.cropRect.y + this.cropRect.height / 3);
|
||||
ctx.lineTo(this.cropRect.x + this.cropRect.width, this.cropRect.y + this.cropRect.height / 3);
|
||||
ctx.moveTo(this.cropRect.x, this.cropRect.y + (this.cropRect.height * 2) / 3);
|
||||
ctx.lineTo(this.cropRect.x + this.cropRect.width, this.cropRect.y + (this.cropRect.height * 2) / 3);
|
||||
ctx.stroke();
|
||||
|
||||
// Draw resize handles
|
||||
const handles = this.getHandlePositions();
|
||||
overlayCtx.fillStyle = '#ffffff';
|
||||
overlayCtx.strokeStyle = '#000000';
|
||||
overlayCtx.lineWidth = 1;
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
for (const pos of Object.values(handles)) {
|
||||
overlayCtx.fillRect(
|
||||
ctx.fillRect(
|
||||
pos.x - this.handleSize / 2,
|
||||
pos.y - this.handleSize / 2,
|
||||
this.handleSize,
|
||||
this.handleSize
|
||||
);
|
||||
overlayCtx.strokeRect(
|
||||
ctx.strokeRect(
|
||||
pos.x - this.handleSize / 2,
|
||||
pos.y - this.handleSize / 2,
|
||||
this.handleSize,
|
||||
@@ -325,11 +307,8 @@ export class CropTool extends BaseTool {
|
||||
);
|
||||
}
|
||||
|
||||
// Draw overlay on main canvas
|
||||
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
ctx.drawImage(this.overlayCanvas, 0, 0);
|
||||
// Restore context state
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
getCursor(): string {
|
||||
@@ -371,6 +350,6 @@ export class CropTool extends BaseTool {
|
||||
width: ctx.canvas.width,
|
||||
height: ctx.canvas.height,
|
||||
};
|
||||
this.overlayCanvas = null;
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user