Spaces:
Runtime error
Runtime error
| /** | |
| * Shared stroke pipeline for brush / eraser / inpaint. | |
| * | |
| * Per-sample stamping happens in `_strokeTo` (still in galleryEditor.js | |
| * because it touches a lot of pixel-pass internals). This module owns | |
| * the begin / continue / end orchestration around it: | |
| * | |
| * - begin: capture the inpaint-erase flag for the stroke, ensure a | |
| * mask sub-layer exists when inpaint runs against an empty | |
| * layer, push an undo entry with a tool-specific label, then | |
| * kick off the first stamp. | |
| * - continue: forward the new cursor position to `_strokeTo`. | |
| * - end: clear the drawing flag, composite, sync any tool indicators | |
| * that reflect mask state. | |
| * | |
| * Clone has its own begin (see tools/clone.js) but reuses `continue` | |
| * and `end` because once a clone stroke is in progress, the pipeline | |
| * is identical. | |
| * | |
| * @param {{ | |
| * saveState: (label: string) => void, | |
| * strokeTo: (x: number, y: number) => void, | |
| * composite: () => void, | |
| * getActiveMaskLayer: () => object | null, | |
| * activeParentLayer: () => object | null, | |
| * ensureActiveMaskLayer: () => object | null, | |
| * createLayer: (name: string, w: number, h: number) => object, | |
| * renderLayerPanel: () => void, | |
| * syncToolClearIndicators: () => void, | |
| * }} deps | |
| */ | |
| import { state } from '../state.js'; | |
| import { canvasCoords } from '../canvas-coords.js'; | |
| const STROKE_TOOLS = new Set(['brush', 'eraser', 'inpaint']); | |
| function strokeLabel(tool) { | |
| if (tool === 'brush') return 'Brush stroke'; | |
| if (tool === 'eraser') return 'Eraser stroke'; | |
| if (tool === 'inpaint') return state.inpaintEraseStroke ? 'Erase mask' : 'Paint mask'; | |
| return 'Stroke'; | |
| } | |
| export function createStrokeTool({ | |
| saveState, strokeTo, composite, | |
| getActiveMaskLayer, activeParentLayer, ensureActiveMaskLayer, createLayer, | |
| renderLayerPanel, syncToolClearIndicators, | |
| }) { | |
| return { | |
| /** | |
| * Begin a stroke. Returns true if the dispatcher should consider | |
| * the event handled (i.e. tool is one of brush/eraser/inpaint). | |
| */ | |
| tryBegin(e) { | |
| if (!STROKE_TOOLS.has(state.tool)) return false; | |
| // Capture the inpaint-erase flag for this stroke. Ctrl+Alt | |
| // pressed at pointerdown flips the persistent toggle for one | |
| // stroke only. | |
| if (state.tool === 'inpaint') { | |
| const flip = e && e.ctrlKey && e.altKey; | |
| state.inpaintEraseStroke = flip ? !state.inpaintEraseMode : state.inpaintEraseMode; | |
| // Make sure we're painting onto an existing mask sub-layer. If | |
| // there's no parent layer at all, create one first so a totally | |
| // empty canvas can accept an inpaint stroke. | |
| if (!getActiveMaskLayer()) { | |
| let parent = activeParentLayer(); | |
| if (!parent) { | |
| parent = createLayer('Layer 1', state.imgWidth, state.imgHeight); | |
| state.layers.push(parent); | |
| state.activeLayerId = parent.id; | |
| } | |
| if (parent.masks && parent.masks.length) { | |
| parent.activeMaskId = parent.masks[parent.masks.length - 1].id; | |
| const m = getActiveMaskLayer(); | |
| if (m) { | |
| state.maskCanvas = m.canvas; | |
| state.maskCtx = m.ctx; | |
| renderLayerPanel(); | |
| } | |
| } else { | |
| const mk = ensureActiveMaskLayer(); | |
| if (mk) { | |
| state.maskCanvas = mk.canvas; | |
| state.maskCtx = mk.ctx; | |
| renderLayerPanel(); | |
| } | |
| } | |
| } | |
| } | |
| saveState(strokeLabel(state.tool)); | |
| state.drawing = true; | |
| const coords = canvasCoords(e, state.mainCanvas); | |
| state.lastX = coords.x; | |
| state.lastY = coords.y; | |
| strokeTo(coords.x, coords.y); | |
| return true; | |
| }, | |
| /** | |
| * Forward an in-progress stroke. Returns true if a stroke is | |
| * actually in progress (dispatcher should short-circuit). | |
| */ | |
| tryContinue(e) { | |
| if (!state.drawing) return false; | |
| e.preventDefault(); | |
| const coords = canvasCoords(e, state.mainCanvas); | |
| strokeTo(coords.x, coords.y); | |
| return true; | |
| }, | |
| /** | |
| * Wrap up an in-progress stroke. Returns true if there was one. | |
| */ | |
| tryEnd() { | |
| if (!state.drawing) return false; | |
| const wasDrawingInpaint = state.tool === 'inpaint'; | |
| state.drawing = false; | |
| composite(); | |
| if (wasDrawingInpaint) syncToolClearIndicators(); | |
| return true; | |
| }, | |
| }; | |
| } | |