diff --git a/gui/src/components/Inpaint.tsx b/gui/src/components/Inpaint.tsx index 22d79ffa..8d84df87 100644 --- a/gui/src/components/Inpaint.tsx +++ b/gui/src/components/Inpaint.tsx @@ -47,15 +47,6 @@ export function Inpaint(props: InpaintProps) { onSuccess: () => query.invalidateQueries({ queryKey: 'ready' }), }); - useEffect(function changeSource() { - // draw the source to the canvas if the mask has not been set - if (doesExist(params.source) && doesExist(params.mask) === false) { - setInpaint({ - mask: params.source, - }); - } - }, [params.source]); - return - { - setInpaint({ - mask, - }); - }} /> + { + setInpaint({ + mask, + }); + }} + /> } /> ; source?: Maybe; onSave: (blob: Blob) => void; } export function MaskCanvas(props: MaskCanvasProps) { - const { config, source } = props; + const { base, config, source } = props; function floodMask(flood: FloodFn) { - const canvas = mustExist(canvasRef.current); - const ctx = mustExist(canvas.getContext('2d')); - const image = ctx.getImageData(0, 0, canvas.width, canvas.height); + const buffer = mustExist(bufferRef.current); + const ctx = mustExist(buffer.getContext('2d')); + const image = ctx.getImageData(0, 0, buffer.width, buffer.height); const pixels = image.data; - for (let x = 0; x < canvas.width; ++x) { - for (let y = 0; y < canvas.height; ++y) { - const i = (y * canvas.width * PIXEL_SIZE) + (x * PIXEL_SIZE); + for (let x = 0; x < buffer.width; ++x) { + for (let y = 0; y < buffer.height; ++y) { + const i = (y * buffer.width * PIXEL_SIZE) + (x * PIXEL_SIZE); const hue = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / PIXEL_WEIGHT; const final = flood(hue); @@ -92,18 +94,29 @@ export function MaskCanvas(props: MaskCanvasProps) { } function saveMask(): void { - if (doesExist(canvasRef.current)) { - if (state.current === MASK_STATE.clean) { + if (doesExist(bufferRef.current)) { + if (maskState.current === MASK_STATE.clean) { return; } - canvasRef.current.toBlob((blob) => { - state.current = MASK_STATE.clean; + bufferRef.current.toBlob((blob) => { + maskState.current = MASK_STATE.clean; props.onSave(mustExist(blob)); }); } } + function drawBuffer() { + if (doesExist(bufferRef.current) && doesExist(canvasRef.current)) { + const dest = mustExist(canvasRef.current); + const ctx = mustExist(dest.getContext('2d')); + + ctx.clearRect(0, 0, dest.width, dest.height); + ctx.globalAlpha = MASK_OPACITY; + ctx.drawImage(bufferRef.current, 0, 0); + } + } + function drawCircle(ctx: CanvasRenderingContext2D, point: Point): void { ctx.beginPath(); ctx.arc(point.x, point.y, brushSize, 0, FULL_CIRCLE); @@ -113,10 +126,12 @@ export function MaskCanvas(props: MaskCanvasProps) { function drawSource(file: Blob): void { const image = new Image(); image.onload = () => { - const canvas = mustExist(canvasRef.current); - const ctx = mustExist(canvas.getContext('2d')); + const buffer = mustExist(bufferRef.current); + const ctx = mustExist(buffer.getContext('2d')); ctx.drawImage(image, 0, 0); URL.revokeObjectURL(src); + + drawBuffer(); }; const src = URL.createObjectURL(file); @@ -124,27 +139,30 @@ export function MaskCanvas(props: MaskCanvasProps) { } function finishPainting() { - if (state.current === MASK_STATE.painting) { - state.current = MASK_STATE.dirty; + if (maskState.current === MASK_STATE.painting) { + maskState.current = MASK_STATE.dirty; save(); } } const save = useMemo(() => throttle(saveMask, SAVE_TIME), []); + // eslint-disable-next-line no-null/no-null + const bufferRef = useRef(null); // eslint-disable-next-line no-null/no-null const canvasRef = useRef(null); // painting state - const state = useRef(MASK_STATE.clean); + const maskState = useRef(MASK_STATE.clean); + const [background, setBackground] = useState(); const [clicks, setClicks] = useState>([]); const [brushColor, setBrushColor] = useState(DEFAULT_BRUSH.color); const [brushSize, setBrushSize] = useState(DEFAULT_BRUSH.size); useEffect(() => { // including clicks.length prevents the initial render from saving a blank canvas - if (doesExist(canvasRef.current) && state.current === MASK_STATE.painting && clicks.length > 0) { - const ctx = mustExist(canvasRef.current.getContext('2d')); + if (doesExist(bufferRef.current) && maskState.current === MASK_STATE.painting && clicks.length > 0) { + const ctx = mustExist(bufferRef.current.getContext('2d')); ctx.fillStyle = grayToRGB(brushColor); for (const click of clicks) { @@ -152,34 +170,61 @@ export function MaskCanvas(props: MaskCanvasProps) { } clicks.length = 0; + drawBuffer(); } }, [clicks.length]); useEffect(() => { - if (state.current === MASK_STATE.dirty) { + if (maskState.current === MASK_STATE.dirty) { save(); } - }, [state.current]); + }, [maskState.current]); useEffect(() => { - if (doesExist(canvasRef.current) && doesExist(source)) { + if (doesExist(bufferRef.current) && doesExist(source)) { drawSource(source); } }, [source]); + useEffect(() => { + if (doesExist(base)) { + if (doesExist(background)) { + URL.revokeObjectURL(background); + } + + setBackground(URL.createObjectURL(base)); + } + }, [base]); + + const styles: React.CSSProperties = { + maxHeight: config.height.default, + maxWidth: config.width.default, + }; + + if (doesExist(background)) { + styles.backgroundImage = `url(${background})`; + } + return + { const canvas = mustExist(canvasRef.current); const bounds = canvas.getBoundingClientRect(); - const ctx = mustExist(canvas.getContext('2d')); + + const buffer = mustExist(bufferRef.current); + const ctx = mustExist(buffer.getContext('2d')); ctx.fillStyle = grayToRGB(brushColor); drawCircle(ctx, { @@ -187,17 +232,17 @@ export function MaskCanvas(props: MaskCanvasProps) { y: event.clientY - bounds.top, }); - state.current = MASK_STATE.dirty; + maskState.current = MASK_STATE.dirty; save(); }} onMouseDown={() => { - state.current = MASK_STATE.painting; + maskState.current = MASK_STATE.painting; }} onMouseLeave={finishPainting} onMouseOut={finishPainting} onMouseUp={finishPainting} onMouseMove={(event) => { - if (state.current === MASK_STATE.painting) { + if (maskState.current === MASK_STATE.painting) { const canvas = mustExist(canvasRef.current); const bounds = canvas.getBoundingClientRect();