|
|
|
@ -1,6 +1,6 @@ |
|
|
|
"use client"; |
|
|
|
|
|
|
|
import { useCallback, useEffect, useRef, useState } from "react"; |
|
|
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"; |
|
|
|
import type { RefObject } from "react"; |
|
|
|
|
|
|
|
export interface MediaInpaintingFrameRect { |
|
|
|
@ -16,6 +16,10 @@ export interface MediaInpaintingMaskPayload { |
|
|
|
height: number; |
|
|
|
} |
|
|
|
|
|
|
|
export interface MediaInpaintingOverlayHandle { |
|
|
|
exportMask: () => Promise<MediaInpaintingMaskPayload | null>; |
|
|
|
} |
|
|
|
|
|
|
|
export type MediaInpaintingDrawTool = "brush" | "rectangle"; |
|
|
|
|
|
|
|
interface DisplayRect { |
|
|
|
@ -36,12 +40,16 @@ interface MediaInpaintingOverlayProps { |
|
|
|
clearSignal?: number; |
|
|
|
undoSignal?: number; |
|
|
|
redoSignal?: number; |
|
|
|
onMaskChange?: (payload: MediaInpaintingMaskPayload | null) => void; |
|
|
|
onFrameChange?: (rect: MediaInpaintingFrameRect | null) => void; |
|
|
|
} |
|
|
|
|
|
|
|
const MAX_HISTORY = 30; |
|
|
|
|
|
|
|
interface MaskHistoryEntry { |
|
|
|
visible: ImageData; |
|
|
|
mask: ImageData; |
|
|
|
} |
|
|
|
|
|
|
|
function clamp(value: number, min: number, max: number) { |
|
|
|
return Math.min(max, Math.max(min, value)); |
|
|
|
} |
|
|
|
@ -69,16 +77,6 @@ async function canvasToPngBlob(canvas: HTMLCanvasElement): Promise<Blob> { |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
function canvasHasVisiblePixels(canvas: HTMLCanvasElement): boolean { |
|
|
|
const context = canvas.getContext("2d"); |
|
|
|
if (!context) return false; |
|
|
|
const pixels = context.getImageData(0, 0, canvas.width, canvas.height).data; |
|
|
|
for (let index = 3; index < pixels.length; index += 4) { |
|
|
|
if (pixels[index] > 0) return true; |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
function createCheckerPattern(context: CanvasRenderingContext2D) { |
|
|
|
const size = 40; |
|
|
|
const tile = document.createElement("canvas"); |
|
|
|
@ -95,7 +93,7 @@ function createCheckerPattern(context: CanvasRenderingContext2D) { |
|
|
|
return context.createPattern(tile, "repeat"); |
|
|
|
} |
|
|
|
|
|
|
|
export function MediaInpaintingOverlay({ |
|
|
|
export const MediaInpaintingOverlay = forwardRef<MediaInpaintingOverlayHandle, MediaInpaintingOverlayProps>(function MediaInpaintingOverlay({ |
|
|
|
mediaElementRef, |
|
|
|
naturalWidth, |
|
|
|
naturalHeight, |
|
|
|
@ -106,26 +104,48 @@ export function MediaInpaintingOverlay({ |
|
|
|
clearSignal = 0, |
|
|
|
undoSignal = 0, |
|
|
|
redoSignal = 0, |
|
|
|
onMaskChange, |
|
|
|
onFrameChange, |
|
|
|
}: MediaInpaintingOverlayProps) { |
|
|
|
}, ref) { |
|
|
|
const overlayRef = useRef<HTMLDivElement>(null); |
|
|
|
const canvasRef = useRef<HTMLCanvasElement>(null); |
|
|
|
const previewCanvasRef = useRef<HTMLCanvasElement>(null); |
|
|
|
const maskCanvasRef = useRef<HTMLCanvasElement | null>(null); |
|
|
|
const cursorRef = useRef<HTMLDivElement>(null); |
|
|
|
const drawingRef = useRef(false); |
|
|
|
const lastPointRef = useRef<{ x: number; y: number } | null>(null); |
|
|
|
const rectangleStartRef = useRef<{ x: number; y: number } | null>(null); |
|
|
|
const historyRef = useRef<ImageData[]>([]); |
|
|
|
const redoRef = useRef<ImageData[]>([]); |
|
|
|
const historyRef = useRef<MaskHistoryEntry[]>([]); |
|
|
|
const redoRef = useRef<MaskHistoryEntry[]>([]); |
|
|
|
const checkerPatternRef = useRef<CanvasPattern | null>(null); |
|
|
|
const [bounds, setBounds] = useState<DisplayRect | null>(null); |
|
|
|
const [cursorPoint, setCursorPoint] = useState<{ x: number; y: number } | null>(null); |
|
|
|
const [rectanglePreview, setRectanglePreview] = useState<{ start: { x: number; y: number }; end: { x: number; y: number } } | null>(null); |
|
|
|
const [naturalDimensions, setNaturalDimensions] = useState<{ width: number; height: number } | null>(() => |
|
|
|
validDimensions(naturalWidth, naturalHeight) |
|
|
|
); |
|
|
|
const [hasMask, setHasMask] = useState(false); |
|
|
|
const activeDrawTool = eraseMode ? "brush" : drawTool; |
|
|
|
|
|
|
|
const resetCanvases = useCallback(() => { |
|
|
|
const canvas = canvasRef.current; |
|
|
|
if (!canvas || !naturalDimensions) return; |
|
|
|
canvas.width = naturalDimensions.width; |
|
|
|
canvas.height = naturalDimensions.height; |
|
|
|
canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
|
|
const maskCanvas = maskCanvasRef.current ?? document.createElement("canvas"); |
|
|
|
maskCanvas.width = naturalDimensions.width; |
|
|
|
maskCanvas.height = naturalDimensions.height; |
|
|
|
maskCanvas.getContext("2d")?.clearRect(0, 0, maskCanvas.width, maskCanvas.height); |
|
|
|
maskCanvasRef.current = maskCanvas; |
|
|
|
|
|
|
|
historyRef.current = []; |
|
|
|
redoRef.current = []; |
|
|
|
checkerPatternRef.current = null; |
|
|
|
}, [naturalDimensions]); |
|
|
|
|
|
|
|
const setCanvasElement = useCallback((canvas: HTMLCanvasElement | null) => { |
|
|
|
canvasRef.current = canvas; |
|
|
|
if (canvas) resetCanvases(); |
|
|
|
}, [resetCanvases]); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
const explicitDimensions = validDimensions(naturalWidth, naturalHeight); |
|
|
|
if (explicitDimensions) { |
|
|
|
@ -184,87 +204,45 @@ export function MediaInpaintingOverlay({ |
|
|
|
}, [measureBounds, onFrameChange]); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
const canvas = canvasRef.current; |
|
|
|
if (!canvas || !naturalDimensions) return; |
|
|
|
canvas.width = naturalDimensions.width; |
|
|
|
canvas.height = naturalDimensions.height; |
|
|
|
const context = canvas.getContext("2d"); |
|
|
|
context?.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
const previewCanvas = previewCanvasRef.current; |
|
|
|
if (previewCanvas) { |
|
|
|
previewCanvas.width = naturalDimensions.width; |
|
|
|
previewCanvas.height = naturalDimensions.height; |
|
|
|
previewCanvas.getContext("2d")?.clearRect(0, 0, previewCanvas.width, previewCanvas.height); |
|
|
|
} |
|
|
|
historyRef.current = []; |
|
|
|
redoRef.current = []; |
|
|
|
setHasMask(false); |
|
|
|
onMaskChange?.(null); |
|
|
|
}, [naturalDimensions, onMaskChange]); |
|
|
|
|
|
|
|
const renderPreview = useCallback(() => { |
|
|
|
const maskCanvas = canvasRef.current; |
|
|
|
const previewCanvas = previewCanvasRef.current; |
|
|
|
const context = previewCanvas?.getContext("2d"); |
|
|
|
if (!maskCanvas || !previewCanvas || !context) return; |
|
|
|
if (previewCanvas.width !== maskCanvas.width || previewCanvas.height !== maskCanvas.height) { |
|
|
|
previewCanvas.width = maskCanvas.width; |
|
|
|
previewCanvas.height = maskCanvas.height; |
|
|
|
} |
|
|
|
|
|
|
|
context.clearRect(0, 0, previewCanvas.width, previewCanvas.height); |
|
|
|
if (!canvasHasVisiblePixels(maskCanvas)) return; |
|
|
|
|
|
|
|
const pattern = createCheckerPattern(context); |
|
|
|
context.save(); |
|
|
|
context.fillStyle = pattern ?? "rgba(230,233,238,0.9)"; |
|
|
|
context.fillRect(0, 0, previewCanvas.width, previewCanvas.height); |
|
|
|
context.globalCompositeOperation = "destination-in"; |
|
|
|
context.drawImage(maskCanvas, 0, 0); |
|
|
|
context.globalCompositeOperation = "source-atop"; |
|
|
|
context.fillStyle = "rgba(255,255,255,0.28)"; |
|
|
|
context.fillRect(0, 0, previewCanvas.width, previewCanvas.height); |
|
|
|
context.restore(); |
|
|
|
}, []); |
|
|
|
resetCanvases(); |
|
|
|
}, [resetCanvases]); |
|
|
|
|
|
|
|
const exportMask = useCallback(async () => { |
|
|
|
const sourceCanvas = canvasRef.current; |
|
|
|
if (!sourceCanvas || !naturalDimensions || !canvasHasVisiblePixels(sourceCanvas)) { |
|
|
|
setHasMask(false); |
|
|
|
onMaskChange?.(null); |
|
|
|
return; |
|
|
|
} |
|
|
|
const sourceCanvas = maskCanvasRef.current; |
|
|
|
if (!sourceCanvas || !naturalDimensions) return null; |
|
|
|
|
|
|
|
const exportCanvas = document.createElement("canvas"); |
|
|
|
exportCanvas.width = naturalDimensions.width; |
|
|
|
exportCanvas.height = naturalDimensions.height; |
|
|
|
const context = exportCanvas.getContext("2d"); |
|
|
|
if (!context) { |
|
|
|
onMaskChange?.(null); |
|
|
|
return; |
|
|
|
} |
|
|
|
if (!context) return null; |
|
|
|
|
|
|
|
context.fillStyle = "#000"; |
|
|
|
context.fillRect(0, 0, exportCanvas.width, exportCanvas.height); |
|
|
|
context.drawImage(sourceCanvas, 0, 0); |
|
|
|
const blob = await canvasToPngBlob(exportCanvas); |
|
|
|
setHasMask(true); |
|
|
|
onMaskChange?.({ |
|
|
|
return { |
|
|
|
blob, |
|
|
|
width: exportCanvas.width, |
|
|
|
height: exportCanvas.height, |
|
|
|
}); |
|
|
|
}, [naturalDimensions, onMaskChange]); |
|
|
|
}; |
|
|
|
}, [naturalDimensions]); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
void exportMask(); |
|
|
|
}, [exportMask]); |
|
|
|
useImperativeHandle(ref, () => ({ exportMask }), [exportMask]); |
|
|
|
|
|
|
|
const pushHistory = useCallback(() => { |
|
|
|
const canvas = canvasRef.current; |
|
|
|
const maskCanvas = maskCanvasRef.current; |
|
|
|
const context = canvas?.getContext("2d"); |
|
|
|
if (!canvas || !context) return; |
|
|
|
historyRef.current = [...historyRef.current.slice(-(MAX_HISTORY - 1)), context.getImageData(0, 0, canvas.width, canvas.height)]; |
|
|
|
const maskContext = maskCanvas?.getContext("2d"); |
|
|
|
if (!canvas || !maskCanvas || !context || !maskContext) return; |
|
|
|
historyRef.current = [ |
|
|
|
...historyRef.current.slice(-(MAX_HISTORY - 1)), |
|
|
|
{ |
|
|
|
visible: context.getImageData(0, 0, canvas.width, canvas.height), |
|
|
|
mask: maskContext.getImageData(0, 0, maskCanvas.width, maskCanvas.height), |
|
|
|
}, |
|
|
|
]; |
|
|
|
redoRef.current = []; |
|
|
|
}, []); |
|
|
|
|
|
|
|
@ -295,22 +273,48 @@ export function MediaInpaintingOverlay({ |
|
|
|
}; |
|
|
|
}, [bounds]); |
|
|
|
|
|
|
|
const withBrushComposite = useCallback((context: CanvasRenderingContext2D, draw: () => void) => { |
|
|
|
const updateCursor = useCallback((point: { x: number; y: number } | null) => { |
|
|
|
const cursor = cursorRef.current; |
|
|
|
if (!cursor) return; |
|
|
|
if (!point) { |
|
|
|
cursor.style.display = "none"; |
|
|
|
return; |
|
|
|
} |
|
|
|
cursor.style.display = "block"; |
|
|
|
cursor.style.transform = `translate(${point.x}px, ${point.y}px) translate(-50%, -50%)`; |
|
|
|
}, []); |
|
|
|
|
|
|
|
const withBrushComposite = useCallback((draw: (context: CanvasRenderingContext2D) => void) => { |
|
|
|
const canvas = canvasRef.current; |
|
|
|
const maskCanvas = maskCanvasRef.current; |
|
|
|
const context = canvas?.getContext("2d"); |
|
|
|
const maskContext = maskCanvas?.getContext("2d"); |
|
|
|
if (!canvas || !maskCanvas || !context || !maskContext) return; |
|
|
|
|
|
|
|
context.save(); |
|
|
|
context.globalCompositeOperation = eraseMode ? "destination-out" : "source-over"; |
|
|
|
context.fillStyle = "#fff"; |
|
|
|
draw(); |
|
|
|
if (eraseMode) { |
|
|
|
draw(context); |
|
|
|
} else { |
|
|
|
if (!checkerPatternRef.current) { |
|
|
|
checkerPatternRef.current = createCheckerPattern(context); |
|
|
|
} |
|
|
|
context.fillStyle = checkerPatternRef.current ?? "rgba(230,233,238,0.9)"; |
|
|
|
draw(context); |
|
|
|
context.fillStyle = "rgba(255,255,255,0.28)"; |
|
|
|
draw(context); |
|
|
|
} |
|
|
|
context.restore(); |
|
|
|
setHasMask(true); |
|
|
|
renderPreview(); |
|
|
|
}, [eraseMode, renderPreview]); |
|
|
|
|
|
|
|
const stampBrush = useCallback((point: { x: number; y: number }) => { |
|
|
|
const canvas = canvasRef.current; |
|
|
|
const context = canvas?.getContext("2d"); |
|
|
|
if (!canvas || !context) return; |
|
|
|
maskContext.save(); |
|
|
|
maskContext.globalCompositeOperation = eraseMode ? "destination-out" : "source-over"; |
|
|
|
maskContext.fillStyle = "#fff"; |
|
|
|
draw(maskContext); |
|
|
|
maskContext.restore(); |
|
|
|
}, [eraseMode]); |
|
|
|
|
|
|
|
withBrushComposite(context, () => { |
|
|
|
const stampBrush = useCallback((point: { x: number; y: number }) => { |
|
|
|
withBrushComposite((context) => { |
|
|
|
const halfSize = brushSize / 2; |
|
|
|
context.beginPath(); |
|
|
|
context.arc(point.x, point.y, halfSize, 0, Math.PI * 2); |
|
|
|
@ -319,17 +323,13 @@ export function MediaInpaintingOverlay({ |
|
|
|
}, [brushSize, withBrushComposite]); |
|
|
|
|
|
|
|
const fillRectangle = useCallback((start: { x: number; y: number }, end: { x: number; y: number }) => { |
|
|
|
const canvas = canvasRef.current; |
|
|
|
const context = canvas?.getContext("2d"); |
|
|
|
if (!canvas || !context) return; |
|
|
|
|
|
|
|
const left = Math.min(start.x, end.x); |
|
|
|
const top = Math.min(start.y, end.y); |
|
|
|
const width = Math.abs(end.x - start.x); |
|
|
|
const height = Math.abs(end.y - start.y); |
|
|
|
if (width <= 0 || height <= 0) return; |
|
|
|
|
|
|
|
withBrushComposite(context, () => { |
|
|
|
withBrushComposite((context) => { |
|
|
|
context.fillRect(left, top, width, height); |
|
|
|
}); |
|
|
|
}, [withBrushComposite]); |
|
|
|
@ -355,7 +355,7 @@ export function MediaInpaintingOverlay({ |
|
|
|
const point = pointFromEvent(event); |
|
|
|
if (!point) return; |
|
|
|
const displayPoint = displayPointFromEvent(event); |
|
|
|
if (displayPoint) setCursorPoint(displayPoint); |
|
|
|
if (displayPoint) updateCursor(displayPoint); |
|
|
|
event.preventDefault(); |
|
|
|
event.stopPropagation(); |
|
|
|
pushHistory(); |
|
|
|
@ -371,7 +371,7 @@ export function MediaInpaintingOverlay({ |
|
|
|
|
|
|
|
const handlePointerMove = useCallback((event: React.PointerEvent) => { |
|
|
|
const displayPoint = displayPointFromEvent(event); |
|
|
|
if (displayPoint) setCursorPoint(displayPoint); |
|
|
|
if (displayPoint) updateCursor(displayPoint); |
|
|
|
if (!drawingRef.current || busy) return; |
|
|
|
const point = pointFromEvent(event); |
|
|
|
if (!point) return; |
|
|
|
@ -383,17 +383,17 @@ export function MediaInpaintingOverlay({ |
|
|
|
return; |
|
|
|
} |
|
|
|
drawTo(point); |
|
|
|
}, [activeDrawTool, busy, displayPointFromEvent, drawTo, pointFromEvent]); |
|
|
|
}, [activeDrawTool, busy, displayPointFromEvent, drawTo, pointFromEvent, updateCursor]); |
|
|
|
|
|
|
|
const handlePointerEnter = useCallback((event: React.PointerEvent) => { |
|
|
|
const displayPoint = displayPointFromEvent(event); |
|
|
|
if (displayPoint) setCursorPoint(displayPoint); |
|
|
|
}, [displayPointFromEvent]); |
|
|
|
if (displayPoint) updateCursor(displayPoint); |
|
|
|
}, [displayPointFromEvent, updateCursor]); |
|
|
|
|
|
|
|
const handlePointerLeave = useCallback(() => { |
|
|
|
if (drawingRef.current) return; |
|
|
|
setCursorPoint(null); |
|
|
|
}, []); |
|
|
|
updateCursor(null); |
|
|
|
}, [updateCursor]); |
|
|
|
|
|
|
|
const stopDrawing = useCallback((event: React.PointerEvent) => { |
|
|
|
if (!drawingRef.current) return; |
|
|
|
@ -408,9 +408,8 @@ export function MediaInpaintingOverlay({ |
|
|
|
lastPointRef.current = null; |
|
|
|
rectangleStartRef.current = null; |
|
|
|
const displayPoint = displayPointFromEvent(event); |
|
|
|
if (displayPoint) setCursorPoint(displayPoint); |
|
|
|
void exportMask(); |
|
|
|
}, [activeDrawTool, displayPointFromEvent, exportMask, fillRectangle, pointFromEvent]); |
|
|
|
if (displayPoint) updateCursor(displayPoint); |
|
|
|
}, [activeDrawTool, displayPointFromEvent, fillRectangle, pointFromEvent, updateCursor]); |
|
|
|
|
|
|
|
const displayBrushSize = bounds && naturalDimensions |
|
|
|
? Math.max(8, brushSize * (bounds.width / naturalDimensions.width)) |
|
|
|
@ -427,39 +426,46 @@ export function MediaInpaintingOverlay({ |
|
|
|
useEffect(() => { |
|
|
|
if (clearSignal <= 0) return; |
|
|
|
const canvas = canvasRef.current; |
|
|
|
const maskCanvas = maskCanvasRef.current; |
|
|
|
const context = canvas?.getContext("2d"); |
|
|
|
if (!canvas || !context) return; |
|
|
|
const maskContext = maskCanvas?.getContext("2d"); |
|
|
|
if (!canvas || !maskCanvas || !context || !maskContext) return; |
|
|
|
pushHistory(); |
|
|
|
context.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
renderPreview(); |
|
|
|
setHasMask(false); |
|
|
|
onMaskChange?.(null); |
|
|
|
}, [clearSignal, onMaskChange, pushHistory, renderPreview]); |
|
|
|
maskContext.clearRect(0, 0, maskCanvas.width, maskCanvas.height); |
|
|
|
}, [clearSignal, pushHistory]); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
if (undoSignal <= 0) return; |
|
|
|
const canvas = canvasRef.current; |
|
|
|
const maskCanvas = maskCanvasRef.current; |
|
|
|
const context = canvas?.getContext("2d"); |
|
|
|
const maskContext = maskCanvas?.getContext("2d"); |
|
|
|
const previous = historyRef.current.pop(); |
|
|
|
if (!canvas || !context || !previous) return; |
|
|
|
redoRef.current.push(context.getImageData(0, 0, canvas.width, canvas.height)); |
|
|
|
context.putImageData(previous, 0, 0); |
|
|
|
renderPreview(); |
|
|
|
void exportMask(); |
|
|
|
}, [exportMask, renderPreview, undoSignal]); |
|
|
|
if (!canvas || !maskCanvas || !context || !maskContext || !previous) return; |
|
|
|
redoRef.current.push({ |
|
|
|
visible: context.getImageData(0, 0, canvas.width, canvas.height), |
|
|
|
mask: maskContext.getImageData(0, 0, maskCanvas.width, maskCanvas.height), |
|
|
|
}); |
|
|
|
context.putImageData(previous.visible, 0, 0); |
|
|
|
maskContext.putImageData(previous.mask, 0, 0); |
|
|
|
}, [undoSignal]); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
if (redoSignal <= 0) return; |
|
|
|
const canvas = canvasRef.current; |
|
|
|
const maskCanvas = maskCanvasRef.current; |
|
|
|
const context = canvas?.getContext("2d"); |
|
|
|
const maskContext = maskCanvas?.getContext("2d"); |
|
|
|
const next = redoRef.current.pop(); |
|
|
|
if (!canvas || !context || !next) return; |
|
|
|
historyRef.current.push(context.getImageData(0, 0, canvas.width, canvas.height)); |
|
|
|
context.putImageData(next, 0, 0); |
|
|
|
renderPreview(); |
|
|
|
setHasMask(true); |
|
|
|
void exportMask(); |
|
|
|
}, [exportMask, redoSignal, renderPreview]); |
|
|
|
if (!canvas || !maskCanvas || !context || !maskContext || !next) return; |
|
|
|
historyRef.current.push({ |
|
|
|
visible: context.getImageData(0, 0, canvas.width, canvas.height), |
|
|
|
mask: maskContext.getImageData(0, 0, maskCanvas.width, maskCanvas.height), |
|
|
|
}); |
|
|
|
context.putImageData(next.visible, 0, 0); |
|
|
|
maskContext.putImageData(next.mask, 0, 0); |
|
|
|
}, [redoSignal]); |
|
|
|
|
|
|
|
return ( |
|
|
|
<div |
|
|
|
@ -469,8 +475,8 @@ export function MediaInpaintingOverlay({ |
|
|
|
> |
|
|
|
{bounds && naturalDimensions && ( |
|
|
|
<canvas |
|
|
|
ref={canvasRef} |
|
|
|
className={`absolute rounded-[2px] opacity-0 ${activeDrawTool === "rectangle" ? "cursor-default" : "cursor-none"}`} |
|
|
|
ref={setCanvasElement} |
|
|
|
className={`absolute rounded-[2px] ${activeDrawTool === "rectangle" ? "cursor-default" : "cursor-none"}`} |
|
|
|
style={{ |
|
|
|
left: bounds.left, |
|
|
|
top: bounds.top, |
|
|
|
@ -485,27 +491,16 @@ export function MediaInpaintingOverlay({ |
|
|
|
onPointerCancel={stopDrawing} |
|
|
|
/> |
|
|
|
)} |
|
|
|
{bounds && naturalDimensions && ( |
|
|
|
<canvas |
|
|
|
ref={previewCanvasRef} |
|
|
|
className="pointer-events-none absolute rounded-[2px]" |
|
|
|
style={{ |
|
|
|
left: bounds.left, |
|
|
|
top: bounds.top, |
|
|
|
width: bounds.width, |
|
|
|
height: bounds.height, |
|
|
|
}} |
|
|
|
/> |
|
|
|
)} |
|
|
|
{cursorPoint && bounds && naturalDimensions && !busy && activeDrawTool === "brush" ? ( |
|
|
|
{bounds && naturalDimensions && !busy && activeDrawTool === "brush" ? ( |
|
|
|
<div |
|
|
|
ref={cursorRef} |
|
|
|
className="pointer-events-none absolute rounded-full border border-white shadow-[0_0_0_1px_rgba(0,0,0,0.55)]" |
|
|
|
style={{ |
|
|
|
left: cursorPoint.x, |
|
|
|
top: cursorPoint.y, |
|
|
|
left: 0, |
|
|
|
top: 0, |
|
|
|
display: "none", |
|
|
|
width: displayBrushSize, |
|
|
|
height: displayBrushSize, |
|
|
|
transform: "translate(-50%, -50%)", |
|
|
|
background: "transparent", |
|
|
|
}} |
|
|
|
/> |
|
|
|
@ -518,4 +513,4 @@ export function MediaInpaintingOverlay({ |
|
|
|
) : null} |
|
|
|
</div> |
|
|
|
); |
|
|
|
} |
|
|
|
}); |
|
|
|
|