- Add canvas-based mask drawing tools (brush, eraser, rect, lasso) - Add undo/redo history support for mask editing - Integrate inpainting UI into preview player - Add backend API endpoint for inpainting requests - Add MediaService.inpaint method with ComfyUI workflow support - Add Flux inpainting workflows for selfhost and RunningHub 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
108 lines
2.5 KiB
TypeScript
108 lines
2.5 KiB
TypeScript
/**
|
|
* Lasso Tool - Free polygon selection for mask
|
|
*/
|
|
|
|
import { Point } from './brush-tool'
|
|
|
|
export interface LassoToolOptions {
|
|
color: string
|
|
}
|
|
|
|
export class LassoTool {
|
|
private ctx: CanvasRenderingContext2D
|
|
private isDrawing: boolean = false
|
|
private points: Point[] = []
|
|
private options: LassoToolOptions
|
|
private previewCanvas: HTMLCanvasElement | null = null
|
|
|
|
constructor(ctx: CanvasRenderingContext2D, options: LassoToolOptions) {
|
|
this.ctx = ctx
|
|
this.options = options
|
|
}
|
|
|
|
setColor(color: string) {
|
|
this.options.color = color
|
|
}
|
|
|
|
setPreviewCanvas(canvas: HTMLCanvasElement) {
|
|
this.previewCanvas = canvas
|
|
}
|
|
|
|
// Add point on click
|
|
addPoint(point: Point) {
|
|
if (!this.isDrawing) {
|
|
this.isDrawing = true
|
|
this.points = []
|
|
}
|
|
this.points.push(point)
|
|
this.drawPreview()
|
|
}
|
|
|
|
// Draw preview polygon
|
|
private drawPreview() {
|
|
if (!this.previewCanvas || this.points.length === 0) return
|
|
|
|
const previewCtx = this.previewCanvas.getContext('2d')
|
|
if (!previewCtx) return
|
|
|
|
previewCtx.clearRect(0, 0, this.previewCanvas.width, this.previewCanvas.height)
|
|
|
|
// Draw polygon outline
|
|
previewCtx.strokeStyle = this.options.color
|
|
previewCtx.lineWidth = 2
|
|
previewCtx.setLineDash([5, 5])
|
|
|
|
previewCtx.beginPath()
|
|
previewCtx.moveTo(this.points[0].x, this.points[0].y)
|
|
for (let i = 1; i < this.points.length; i++) {
|
|
previewCtx.lineTo(this.points[i].x, this.points[i].y)
|
|
}
|
|
previewCtx.stroke()
|
|
|
|
// Draw points
|
|
this.points.forEach((p, i) => {
|
|
previewCtx.fillStyle = i === 0 ? '#00ff00' : this.options.color
|
|
previewCtx.beginPath()
|
|
previewCtx.arc(p.x, p.y, 4, 0, Math.PI * 2)
|
|
previewCtx.fill()
|
|
})
|
|
}
|
|
|
|
// Close and fill polygon on double-click
|
|
closePath() {
|
|
if (!this.isDrawing || this.points.length < 3) {
|
|
this.cancel()
|
|
return
|
|
}
|
|
|
|
// Fill polygon on mask
|
|
this.ctx.fillStyle = this.options.color
|
|
this.ctx.beginPath()
|
|
this.ctx.moveTo(this.points[0].x, this.points[0].y)
|
|
for (let i = 1; i < this.points.length; i++) {
|
|
this.ctx.lineTo(this.points[i].x, this.points[i].y)
|
|
}
|
|
this.ctx.closePath()
|
|
this.ctx.fill()
|
|
|
|
this.cancel()
|
|
}
|
|
|
|
cancel() {
|
|
this.isDrawing = false
|
|
this.points = []
|
|
if (this.previewCanvas) {
|
|
const ctx = this.previewCanvas.getContext('2d')
|
|
ctx?.clearRect(0, 0, this.previewCanvas.width, this.previewCanvas.height)
|
|
}
|
|
}
|
|
|
|
isActive(): boolean {
|
|
return this.isDrawing
|
|
}
|
|
|
|
getPointCount(): number {
|
|
return this.points.length
|
|
}
|
|
}
|