diff --git a/frontend/src/common/util/roundDownToMultiple.ts b/frontend/src/common/util/roundDownToMultiple.ts index 9143a014a6..850b235629 100644 --- a/frontend/src/common/util/roundDownToMultiple.ts +++ b/frontend/src/common/util/roundDownToMultiple.ts @@ -1,3 +1,7 @@ export const roundDownToMultiple = (num: number, multiple: number): number => { return Math.floor(num / multiple) * multiple; }; + +export const roundToMultiple = (num: number, multiple: number): number => { + return Math.round(num / multiple) * multiple; +}; diff --git a/frontend/src/features/tabs/Inpainting/InpaintingCanvas.tsx b/frontend/src/features/tabs/Inpainting/InpaintingCanvas.tsx index 81a4bc1a0a..840da4d446 100644 --- a/frontend/src/features/tabs/Inpainting/InpaintingCanvas.tsx +++ b/frontend/src/features/tabs/Inpainting/InpaintingCanvas.tsx @@ -1,6 +1,5 @@ // lib import { - KeyboardEvent, MutableRefObject, useCallback, useEffect, @@ -20,7 +19,6 @@ import { setBoundingBoxCoordinate, setCursorPosition, setIsMovingBoundingBox, - setTool, } from './inpaintingSlice'; import { inpaintingCanvasSelector } from './inpaintingSliceSelectors'; @@ -32,7 +30,9 @@ import Cacher from './components/Cacher'; import { Vector2d } from 'konva/lib/types'; import getScaledCursorPosition from './util/getScaledCursorPosition'; import _ from 'lodash'; -import InpaintingBoundingBoxPreview from './components/InpaintingBoundingBoxPreview'; +import InpaintingBoundingBoxPreview, { + InpaintingBoundingBoxPreviewOverlay, +} from './components/InpaintingBoundingBoxPreview'; import { KonvaEventObject } from 'konva/lib/Node'; import KeyboardEventManager from './components/KeyboardEventManager'; @@ -50,13 +50,14 @@ const InpaintingCanvas = () => { shouldInvertMask, shouldShowMask, shouldShowCheckboardTransparency, - maskOpacity, + maskColor, imageToInpaint, isMovingBoundingBox, boundingBoxDimensions, canvasDimensions, boundingBoxCoordinate, stageScale, + shouldShowBoundingBoxFill, } = useAppSelector(inpaintingCanvasSelector); // set the closure'd refs @@ -248,7 +249,7 @@ const InpaintingCanvas = () => { opacity={ shouldShowCheckboardTransparency || shouldInvertMask ? 1 - : maskOpacity + : maskColor.a } ref={maskLayerRef} > @@ -270,8 +271,11 @@ const InpaintingCanvas = () => { /> )} - + + {shouldShowBoundingBoxFill && ( + + )} @@ -284,6 +288,4 @@ const InpaintingCanvas = () => { ); }; -// - export default InpaintingCanvas; diff --git a/frontend/src/features/tabs/Inpainting/InpaintingControls.tsx b/frontend/src/features/tabs/Inpainting/InpaintingControls.tsx index 8028c887f6..f7d510b4f6 100644 --- a/frontend/src/features/tabs/Inpainting/InpaintingControls.tsx +++ b/frontend/src/features/tabs/Inpainting/InpaintingControls.tsx @@ -6,32 +6,29 @@ import { FaPalette, FaPlus, FaRedo, + FaTint, + FaTintSlash, FaUndo, } from 'react-icons/fa'; import { BiHide, BiShow } from 'react-icons/bi'; import { VscSplitHorizontal } from 'react-icons/vsc'; -import { RootState, useAppDispatch, useAppSelector } from '../../../app/store'; +import { useAppDispatch, useAppSelector } from '../../../app/store'; import IAIIconButton from '../../../common/components/IAIIconButton'; import { clearMask, redo, setMaskColor, setBrushSize, - setMaskOpacity, setShouldShowBrushPreview, setTool, undo, setShouldShowMask, setShouldInvertMask, setNeedsRepaint, + setShouldShowBoundingBoxFill, } from './inpaintingSlice'; -import { tabMap } from '../InvokeTabs'; -import { - MdInvertColors, - MdInvertColorsOff, - MdOutlineCloseFullscreen, -} from 'react-icons/md'; +import { MdInvertColors, MdInvertColorsOff } from 'react-icons/md'; import IAISlider from '../../../common/components/IAISlider'; import IAINumberInput from '../../../common/components/IAINumberInput'; import { inpaintingControlsSelector } from './inpaintingSliceSelectors'; @@ -39,14 +36,12 @@ import IAIPopover from '../../../common/components/IAIPopover'; import IAIColorPicker from '../../../common/components/IAIColorPicker'; import { RgbaColor } from 'react-colorful'; import { setShowDualDisplay } from '../../options/optionsSlice'; -import { useEffect } from 'react'; const InpaintingControls = () => { const { tool, brushSize, maskColor, - maskOpacity, shouldInvertMask, shouldShowMask, canUndo, @@ -54,12 +49,17 @@ const InpaintingControls = () => { isMaskEmpty, activeTabName, showDualDisplay, + shouldShowBoundingBoxFill, } = useAppSelector(inpaintingControlsSelector); const dispatch = useAppDispatch(); const toast = useToast(); - // Hotkeys + /** + * Hotkeys + */ + + // Decrease brush size useHotkeys( '[', (e: KeyboardEvent) => { @@ -76,6 +76,7 @@ const InpaintingControls = () => { [activeTabName, shouldShowMask, brushSize] ); + // Increase brush size useHotkeys( ']', (e: KeyboardEvent) => { @@ -88,30 +89,39 @@ const InpaintingControls = () => { [activeTabName, shouldShowMask, brushSize] ); + // Decrease mask opacity useHotkeys( 'shift+[', (e: KeyboardEvent) => { e.preventDefault(); - handleChangeMaskOpacity(Math.max(maskOpacity - 0.05, 0)); + handleChangeMaskColor({ + ...maskColor, + a: Math.max(maskColor.a - 0.05, 0), + }); }, { enabled: activeTabName === 'inpainting' && shouldShowMask, }, - [activeTabName, shouldShowMask, maskOpacity] + [activeTabName, shouldShowMask, maskColor.a] ); + // Increase mask opacity useHotkeys( 'shift+]', (e: KeyboardEvent) => { e.preventDefault(); - handleChangeMaskOpacity(Math.min(maskOpacity + 0.05, 100)); + handleChangeMaskColor({ + ...maskColor, + a: Math.min(maskColor.a + 0.05, 100), + }); }, { enabled: activeTabName === 'inpainting' && shouldShowMask, }, - [activeTabName, shouldShowMask, maskOpacity] + [activeTabName, shouldShowMask, maskColor.a] ); + // Set tool to eraser useHotkeys( 'e', (e: KeyboardEvent) => { @@ -125,6 +135,7 @@ const InpaintingControls = () => { [activeTabName, shouldShowMask] ); + // Set tool to brush useHotkeys( 'b', (e: KeyboardEvent) => { @@ -137,6 +148,7 @@ const InpaintingControls = () => { [activeTabName, shouldShowMask] ); + // Undo useHotkeys( 'cmd+z, control+z', (e: KeyboardEvent) => { @@ -149,6 +161,7 @@ const InpaintingControls = () => { [activeTabName, shouldShowMask, canUndo] ); + // Redo useHotkeys( 'cmd+shift+z, control+shift+z, control+y, cmd+y', (e: KeyboardEvent) => { @@ -161,6 +174,7 @@ const InpaintingControls = () => { [activeTabName, shouldShowMask, canRedo] ); + // Show/hide mask useHotkeys( 'h', (e: KeyboardEvent) => { @@ -173,6 +187,7 @@ const InpaintingControls = () => { [activeTabName, shouldShowMask] ); + // Invert mask useHotkeys( 'shift+m', (e: KeyboardEvent) => { @@ -185,6 +200,7 @@ const InpaintingControls = () => { [activeTabName, shouldInvertMask, shouldShowMask] ); + // Clear mask useHotkeys( 'shift+c', (e: KeyboardEvent) => { @@ -203,6 +219,7 @@ const InpaintingControls = () => { [activeTabName, isMaskEmpty, shouldShowMask] ); + // Toggle split view useHotkeys( 'shift+j', () => { @@ -224,10 +241,6 @@ const InpaintingControls = () => { dispatch(setBrushSize(v)); }; - const handleChangeMaskOpacity = (v: number) => { - dispatch(setMaskOpacity(v)); - }; - const handleToggleShouldShowMask = () => dispatch(setShouldShowMask(!shouldShowMask)); @@ -242,10 +255,8 @@ const InpaintingControls = () => { dispatch(setShouldShowBrushPreview(false)); }; - const handleChangeBrushColor = (newColor: RgbaColor) => { - const { r, g, b, a: maskOpacity } = newColor; - dispatch(setMaskColor({ r, g, b })); - dispatch(setMaskOpacity(maskOpacity)); + const handleChangeMaskColor = (newColor: RgbaColor) => { + dispatch(setMaskColor(newColor)); }; const handleUndo = () => dispatch(undo()); @@ -257,6 +268,10 @@ const InpaintingControls = () => { dispatch(setNeedsRepaint(true)); }; + const handleChangeShouldShowBoundingBoxFill = () => { + dispatch(setShouldShowBoundingBoxFill(!shouldShowBoundingBoxFill)); + }; + return (
@@ -320,8 +335,8 @@ const InpaintingControls = () => { } > { data-selected={showDualDisplay} onClick={handleDualDisplay} /> + : } + data-selected={shouldShowBoundingBoxFill} + onClick={handleChangeShouldShowBoundingBoxFill} + />
diff --git a/frontend/src/features/tabs/Inpainting/components/InpaintingBoundingBoxPreview.tsx b/frontend/src/features/tabs/Inpainting/components/InpaintingBoundingBoxPreview.tsx index 1a2b28ce4a..d469542c6e 100644 --- a/frontend/src/features/tabs/Inpainting/components/InpaintingBoundingBoxPreview.tsx +++ b/frontend/src/features/tabs/Inpainting/components/InpaintingBoundingBoxPreview.tsx @@ -1,13 +1,26 @@ import { createSelector } from '@reduxjs/toolkit'; import Konva from 'konva'; +import { Vector2d } from 'konva/lib/types'; import _ from 'lodash'; import { useEffect, useRef } from 'react'; -import { Group, Rect } from 'react-konva'; -import { RootState, useAppSelector } from '../../../../app/store'; -import { InpaintingState } from '../inpaintingSlice'; +import { Group, Rect, Transformer } from 'react-konva'; +import { + RootState, + useAppDispatch, + useAppSelector, +} from '../../../../app/store'; +import { roundToMultiple } from '../../../../common/util/roundDownToMultiple'; +import { + InpaintingState, + setBoundingBoxCoordinate, + setBoundingBoxDimensions, +} from '../inpaintingSlice'; import { rgbaColorToString } from '../util/colorToString'; -import { DASH_WIDTH, MARCHING_ANTS_SPEED } from '../util/constants'; - +import { + DASH_WIDTH, + // MARCHING_ANTS_SPEED, + TRANSFORMER_ANCHOR_SIZE, +} from '../util/constants'; const boundingBoxPreviewSelector = createSelector( (state: RootState) => state.inpainting, @@ -18,14 +31,18 @@ const boundingBoxPreviewSelector = createSelector( boundingBoxPreviewFill, canvasDimensions, stageScale, + imageToInpaint, } = inpainting; return { boundingBoxCoordinate, boundingBoxDimensions, boundingBoxPreviewFillString: rgbaColorToString(boundingBoxPreviewFill), canvasDimensions, - dash: DASH_WIDTH / stageScale, // scale dash lengths + stageScale, + imageToInpaint, + dash: DASH_WIDTH / stageScale, // scale dash lengths strokeWidth: 1 / stageScale, // scale stroke thickness + anchorSize: TRANSFORMER_ANCHOR_SIZE, }; }, { @@ -38,7 +55,7 @@ const boundingBoxPreviewSelector = createSelector( /** * Shades the area around the mask. */ -const InpaintingBoundingBoxPreviewOverlay = () => { +export const InpaintingBoundingBoxPreviewOverlay = () => { const { boundingBoxCoordinate, boundingBoxDimensions, @@ -68,122 +85,228 @@ const InpaintingBoundingBoxPreviewOverlay = () => { ); }; -/** - * Draws marching ants around the mask. - */ -const InpaintingBoundingBoxPreviewMarchingAnts = () => { - const { boundingBoxCoordinate, boundingBoxDimensions } = useAppSelector( - boundingBoxPreviewSelector - ); +// /** +// * Draws marching ants around the mask. Unused. +// */ +// const _InpaintingBoundingBoxPreviewMarchingAnts = () => { +// const { boundingBoxCoordinate, boundingBoxDimensions } = useAppSelector( +// boundingBoxPreviewSelector +// ); - const blackStrokeRectRef = useRef(null); - const whiteStrokeRectRef = useRef(null); +// const blackStrokeRectRef = useRef(null); +// const whiteStrokeRectRef = useRef(null); + +// useEffect(() => { +// const blackStrokeRect = blackStrokeRectRef.current; +// const whiteStrokeRect = whiteStrokeRectRef.current; + +// const anim = new Konva.Animation((frame) => { +// if (!frame) return; +// blackStrokeRect?.dashOffset( +// -1 * (Math.floor(frame.time / MARCHING_ANTS_SPEED) % 16) +// ); +// whiteStrokeRect?.dashOffset( +// -1 * ((Math.floor(frame.time / MARCHING_ANTS_SPEED) % 16) + 4) +// ); +// }); + +// anim.start(); + +// return () => { +// anim.stop(); +// }; +// }, []); + +// return ( +// +// +// +// +// ); +// }; + +const InpaintingBoundingBoxPreview = () => { + const dispatch = useAppDispatch(); + const { + boundingBoxCoordinate, + boundingBoxDimensions, + strokeWidth, + anchorSize, + stageScale, + imageToInpaint, + } = useAppSelector(boundingBoxPreviewSelector); + + const transformerRef = useRef(null); + const shapeRef = useRef(null); useEffect(() => { - const blackStrokeRect = blackStrokeRectRef.current; - const whiteStrokeRect = whiteStrokeRectRef.current; - - const anim = new Konva.Animation((frame) => { - if (!frame) return; - blackStrokeRect?.dashOffset( - -1 * (Math.floor(frame.time / MARCHING_ANTS_SPEED) % 16) - ); - whiteStrokeRect?.dashOffset( - -1 * ((Math.floor(frame.time / MARCHING_ANTS_SPEED) % 16) + 4) - ); - }); - - anim.start(); - - return () => { - anim.stop(); - }; + if (!transformerRef.current || !shapeRef.current) return; + transformerRef.current.nodes([shapeRef.current]); + transformerRef.current.getLayer()?.batchDraw(); }, []); return ( - - + <> - - ); -}; - -/** - * Draws non-marching ants around the mask. - */ -const InpaintingBoundingBoxPreviewAnts = () => { - const { boundingBoxCoordinate, boundingBoxDimensions, dash, strokeWidth } = - useAppSelector(boundingBoxPreviewSelector); - - return ( - - { + /** + * The Konva Transformer changes the object's anchor point and scale factor, + * not its width and height. We need to un-scale the width and height before + * setting the values. + */ + if (!shapeRef.current) return; + + const rect = shapeRef.current; + + const scaleX = rect.scaleX(); + const scaleY = rect.scaleY(); + + // undo the scaling + const width = Math.round(rect.width() * scaleX); + const height = Math.round(rect.height() * scaleY); + + const x = Math.round(rect.x()); + const y = Math.round(rect.y()); + + dispatch( + setBoundingBoxDimensions({ + width, + height, + }) + ); + + dispatch( + setBoundingBoxCoordinate({ + x, + y, + }) + ); + + // Reset the scale now that the coords/dimensions have been un-scaled + rect.scaleX(1); + rect.scaleY(1); + }} /> - { + /** + * Konva does not transform with width or height. It transforms the anchor point + * and scale factor. This is then sent to the shape's onTransform listeners. + * + * We need to snap the new width to steps of 64 without also snapping the + * coordinates of the bounding box to steps of 64. But because the whole + * stage is scaled, our actual desired step is actually 64 * the stage scale. + */ + + // Get the scaled step + const scaledStep = 64 * stageScale; + + // Difference of the old coords from the nearest multiple the scaled step + const offsetX = oldPos.x % scaledStep; + const offsetY = oldPos.y % scaledStep; + + // Round new position to the nearest multiple of the scaled step + const closestX = roundToMultiple(newPos.x, scaledStep) + offsetX; + const closestY = roundToMultiple(newPos.y, scaledStep) + offsetY; + + // the difference between the old coord and new + const diffX = Math.abs(newPos.x - closestX); + const diffY = Math.abs(newPos.y - closestY); + + // if the difference is less than the scaled step, we want to snap + const didSnapX = diffX < scaledStep; + const didSnapY = diffY < scaledStep; + + // We may not change anything, stash the old position + let newCoordinate = { ...oldPos }; + + // Set the new coords based on what snapped + if (didSnapX && !didSnapY) { + newCoordinate = { + x: closestX, + y: oldPos.y, + }; + } else if (!didSnapX && didSnapY) { + newCoordinate = { + x: oldPos.x, + y: closestY, + }; + } else if (didSnapX && didSnapY) { + newCoordinate = { + x: closestX, + y: closestY, + }; + } + + return newCoordinate; + }} + boundBoxFunc={(oldBoundBox, newBoundBox) => { + /** + * The transformer uses this callback to limit valid transformations. + * Unlike anchorDragBoundFunc, it does get a width and height, so + * the logic to constrain the size of the bounding box is very simple. + */ + + if (!imageToInpaint) return oldBoundBox; + + if ( + newBoundBox.width + newBoundBox.x > imageToInpaint.width || + newBoundBox.height + newBoundBox.y > imageToInpaint.height || + newBoundBox.x < 0 || + newBoundBox.y < 0 + ) { + return oldBoundBox; + } + + return newBoundBox; + }} /> - + ); }; -const boundingBoxPreviewTypeSelector = createSelector( - (state: RootState) => state.inpainting, - (inpainting: InpaintingState) => inpainting.boundingBoxPreviewType -); - -const InpaintingBoundingBoxPreview = () => { - const boundingBoxPreviewType = useAppSelector(boundingBoxPreviewTypeSelector); - - switch (boundingBoxPreviewType) { - case 'overlay': { - return ; - } - case 'ants': { - return ; - } - case 'marchingAnts': { - return ; - } - default: - return null; - } -}; - export default InpaintingBoundingBoxPreview; diff --git a/frontend/src/features/tabs/Inpainting/components/InpaintingCanvasBrushPreview.tsx b/frontend/src/features/tabs/Inpainting/components/InpaintingCanvasBrushPreview.tsx index 6f465a2860..37fc8c8920 100644 --- a/frontend/src/features/tabs/Inpainting/components/InpaintingCanvasBrushPreview.tsx +++ b/frontend/src/features/tabs/Inpainting/components/InpaintingCanvasBrushPreview.tsx @@ -3,7 +3,7 @@ import _ from 'lodash'; import { Circle } from 'react-konva'; import { RootState, useAppSelector } from '../../../../app/store'; import { InpaintingState } from '../inpaintingSlice'; -import { rgbColorToString } from '../util/colorToString'; +import { rgbaColorToRgbString } from '../util/colorToString'; const inpaintingCanvasBrushPreviewSelector = createSelector( (state: RootState) => state.inpainting, @@ -23,7 +23,7 @@ const inpaintingCanvasBrushPreviewSelector = createSelector( height, shouldShowBrushPreview, brushSize, - maskColorString: rgbColorToString(maskColor), + maskColorString: rgbaColorToRgbString(maskColor), tool, }; }, diff --git a/frontend/src/features/tabs/Inpainting/components/InpaintingCanvasLines.tsx b/frontend/src/features/tabs/Inpainting/components/InpaintingCanvasLines.tsx index 13cc785c25..d9cc41038d 100644 --- a/frontend/src/features/tabs/Inpainting/components/InpaintingCanvasLines.tsx +++ b/frontend/src/features/tabs/Inpainting/components/InpaintingCanvasLines.tsx @@ -1,5 +1,6 @@ import { Line } from 'react-konva'; -import { RootState, useAppSelector } from '../../../../app/store'; +import { useAppSelector } from '../../../../app/store'; +import { inpaintingCanvasLinesSelector } from '../inpaintingSliceSelectors'; /** * Draws the lines which comprise the mask. @@ -7,11 +8,9 @@ import { RootState, useAppSelector } from '../../../../app/store'; * Uses globalCompositeOperation to handle the brush and eraser tools. */ const InpaintingCanvasLines = () => { - const { lines, maskColor } = useAppSelector( - (state: RootState) => state.inpainting + const { lines, maskColorString } = useAppSelector( + inpaintingCanvasLinesSelector ); - const { r, g, b } = maskColor; - const maskColorString = `rgb(${r},${g},${b})`; return ( <> diff --git a/frontend/src/features/tabs/Inpainting/components/KeyboardEventManager.tsx b/frontend/src/features/tabs/Inpainting/components/KeyboardEventManager.tsx index 5c42c16c96..4c20af3197 100644 --- a/frontend/src/features/tabs/Inpainting/components/KeyboardEventManager.tsx +++ b/frontend/src/features/tabs/Inpainting/components/KeyboardEventManager.tsx @@ -39,24 +39,10 @@ const KeyboardEventManager = () => { const isFirstEvent = useRef(true); const wasLastEventOverCanvas = useRef(false); + const lastEvent = useRef(null); useEffect(() => { const listener = (e: KeyboardEvent) => { - if (!isCursorOnCanvas) { - wasLastEventOverCanvas.current = false; - - if (isFirstEvent.current) { - isFirstEvent.current = false; - } - - return; - } - - if (isFirstEvent.current) { - wasLastEventOverCanvas.current = true; - isFirstEvent.current = false; - } - if ( !['Alt', ' '].includes(e.key) || activeTabName !== 'inpainting' || @@ -66,8 +52,27 @@ const KeyboardEventManager = () => { return; } - if (!wasLastEventOverCanvas.current) { + // cursor is NOT over canvas + if (!isCursorOnCanvas) { + if (!lastEvent.current) { + lastEvent.current = e; + } + + wasLastEventOverCanvas.current = false; + return; + } + + // cursor is over canvas + + // if this is the first event + if (!lastEvent.current) { wasLastEventOverCanvas.current = true; + lastEvent.current = e; + } + + if (!wasLastEventOverCanvas.current && e.type === 'keyup') { + wasLastEventOverCanvas.current = true; + lastEvent.current = e; return; } @@ -83,9 +88,11 @@ const KeyboardEventManager = () => { break; } } + + lastEvent.current = e; + wasLastEventOverCanvas.current = true; }; - console.log('adding listeners'); document.addEventListener('keydown', listener); document.addEventListener('keyup', listener); diff --git a/frontend/src/features/tabs/Inpainting/inpaintingSlice.ts b/frontend/src/features/tabs/Inpainting/inpaintingSlice.ts index b80590016e..967a90e098 100644 --- a/frontend/src/features/tabs/Inpainting/inpaintingSlice.ts +++ b/frontend/src/features/tabs/Inpainting/inpaintingSlice.ts @@ -1,7 +1,7 @@ import { createSlice } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit'; import { Vector2d } from 'konva/lib/types'; -import { RgbaColor, RgbColor } from 'react-colorful'; +import { RgbaColor } from 'react-colorful'; import * as InvokeAI from '../../../app/invokeai'; import _ from 'lodash'; import { roundDownToMultiple } from '../../../common/util/roundDownToMultiple'; @@ -31,15 +31,15 @@ export type BoundingBoxPreviewType = 'overlay' | 'ants' | 'marchingAnts'; export interface InpaintingState { tool: 'brush' | 'eraser'; brushSize: number; - maskColor: RgbColor; - maskOpacity: number; + maskColor: RgbaColor; cursorPosition: Vector2d | null; canvasDimensions: Dimensions; boundingBoxDimensions: Dimensions; boundingBoxCoordinate: Vector2d; isMovingBoundingBox: boolean; boundingBoxPreviewFill: RgbaColor; - boundingBoxPreviewType: BoundingBoxPreviewType; + shouldShowBoundingBoxFill: boolean; + isBoundingBoxTransforming: boolean; lines: MaskLine[]; pastLines: MaskLine[][]; futureLines: MaskLine[][]; @@ -50,18 +50,19 @@ export interface InpaintingState { imageToInpaint?: InvokeAI.Image; needsRepaint: boolean; stageScale: number; + isDrawing: boolean; } const initialInpaintingState: InpaintingState = { tool: 'brush', brushSize: 50, - maskColor: { r: 255, g: 90, b: 90 }, - maskOpacity: 0.5, + maskColor: { r: 255, g: 90, b: 90, a: 0.5 }, canvasDimensions: { width: 0, height: 0 }, boundingBoxDimensions: { width: 64, height: 64 }, boundingBoxCoordinate: { x: 0, y: 0 }, - boundingBoxPreviewFill: { r: 0, g: 0, b: 0, a: 0.5 }, - boundingBoxPreviewType: 'ants', + boundingBoxPreviewFill: { r: 0, g: 0, b: 0, a: 0.7 }, + shouldShowBoundingBoxFill: false, + isBoundingBoxTransforming: false, cursorPosition: null, lines: [], pastLines: [], @@ -72,6 +73,7 @@ const initialInpaintingState: InpaintingState = { shouldShowBrushPreview: false, isMovingBoundingBox: false, needsRepaint: false, + isDrawing: false, stageScale: 1, }; @@ -142,12 +144,10 @@ export const inpaintingSlice = createSlice({ setShouldShowBrushPreview: (state, action: PayloadAction) => { state.shouldShowBrushPreview = action.payload; }, - setMaskColor: (state, action: PayloadAction) => { + setMaskColor: (state, action: PayloadAction) => { state.maskColor = action.payload; }, - setMaskOpacity: (state, action: PayloadAction) => { - state.maskOpacity = action.payload; - }, + // }, setCursorPosition: (state, action: PayloadAction) => { state.cursorPosition = action.payload; }, @@ -215,34 +215,67 @@ export const inpaintingSlice = createSlice({ }; }, setBoundingBoxDimensions: (state, action: PayloadAction) => { - const { width: boundingBoxWidth, height: boundingBoxHeight } = - action.payload; - const { x: boundingBoxX, y: boundingBoxY } = state.boundingBoxCoordinate; - const { width: canvasWidth, height: canvasHeight } = - state.canvasDimensions; + state.boundingBoxDimensions = action.payload; + // const { width: boundingBoxWidth, height: boundingBoxHeight } = + // action.payload; + // const { x: boundingBoxX, y: boundingBoxY } = state.boundingBoxCoordinate; + // const { width: canvasWidth, height: canvasHeight } = + // state.canvasDimensions; - const overflowX = boundingBoxX + boundingBoxWidth - canvasWidth; - const overflowY = boundingBoxY + boundingBoxHeight - canvasHeight; + // const roundedCanvasWidth = roundDownToMultiple(canvasWidth, 64); + // const roundedCanvasHeight = roundDownToMultiple(canvasHeight, 64); + // const roundedBoundingBoxWidth = roundDownToMultiple(boundingBoxWidth, 64); + // const roundedBoundingBoxHeight = roundDownToMultiple( + // boundingBoxHeight, + // 64 + // ); - const newBoundingBoxX = roundDownToMultiple( - overflowX > 0 ? boundingBoxX - overflowX : boundingBoxX, - 64 - ); + // const overflowX = boundingBoxX + boundingBoxWidth - canvasWidth; + // const overflowY = boundingBoxY + boundingBoxHeight - canvasHeight; - const newBoundingBoxY = roundDownToMultiple( - overflowY > 0 ? boundingBoxY - overflowY : boundingBoxY, - 64 - ); + // const newBoundingBoxWidth = _.clamp( + // roundedBoundingBoxWidth, + // 64, + // roundedCanvasWidth + // ); - state.boundingBoxDimensions = { - width: roundDownToMultiple(boundingBoxWidth, 64), - height: roundDownToMultiple(boundingBoxHeight, 64), - }; + // const newBoundingBoxHeight = _.clamp( + // roundedBoundingBoxHeight, + // 64, + // roundedCanvasHeight + // ); - state.boundingBoxCoordinate = { - x: newBoundingBoxX, - y: newBoundingBoxY, - }; + // const overflowCorrectedX = + // overflowX > 0 ? boundingBoxX - overflowX : boundingBoxX; + + // const overflowCorrectedY = + // overflowY > 0 ? boundingBoxY - overflowY : boundingBoxY; + + // const clampedX = _.clamp( + // overflowCorrectedX, + // 64, + // roundedCanvasWidth - newBoundingBoxWidth + // ); + + // const clampedY = _.clamp( + // overflowCorrectedY, + // 64, + // roundedCanvasHeight - newBoundingBoxHeight + // ); + + // const newBoundingBoxX = roundDownToMultiple(clampedX, 64); + + // const newBoundingBoxY = roundDownToMultiple(clampedY, 64); + + // state.boundingBoxDimensions = { + // width: newBoundingBoxWidth, + // height: newBoundingBoxHeight, + // }; + + // state.boundingBoxCoordinate = { + // x: newBoundingBoxX, + // y: newBoundingBoxY, + // }; }, setBoundingBoxCoordinate: (state, action: PayloadAction) => { state.boundingBoxCoordinate = action.payload; @@ -256,12 +289,6 @@ export const inpaintingSlice = createSlice({ setBoundingBoxPreviewFill: (state, action: PayloadAction) => { state.boundingBoxPreviewFill = action.payload; }, - setBoundingBoxPreviewType: ( - state, - action: PayloadAction - ) => { - state.boundingBoxPreviewType = action.payload; - }, setNeedsRepaint: (state, action: PayloadAction) => { state.needsRepaint = action.payload; }, @@ -269,6 +296,15 @@ export const inpaintingSlice = createSlice({ state.stageScale = action.payload; state.needsRepaint = false; }, + setShouldShowBoundingBoxFill: (state, action: PayloadAction) => { + state.shouldShowBoundingBoxFill = action.payload; + }, + setIsBoundingBoxTransforming: (state, action: PayloadAction) => { + state.isBoundingBoxTransforming = action.payload; + }, + setIsDrawing: (state, action: PayloadAction) => { + state.isDrawing = action.payload; + }, }, }); @@ -283,7 +319,6 @@ export const { setShouldShowBrushPreview, setMaskColor, clearMask, - setMaskOpacity, undo, redo, setCursorPosition, @@ -293,11 +328,13 @@ export const { setBoundingBoxCoordinate, setIsMovingBoundingBox, setBoundingBoxPreviewFill, - setBoundingBoxPreviewType, setNeedsRepaint, setStageScale, toggleTool, toggleIsMovingBoundingBox, + setShouldShowBoundingBoxFill, + setIsBoundingBoxTransforming, + setIsDrawing, } = inpaintingSlice.actions; export default inpaintingSlice.reducer; diff --git a/frontend/src/features/tabs/Inpainting/inpaintingSliceSelectors.ts b/frontend/src/features/tabs/Inpainting/inpaintingSliceSelectors.ts index c58c9bb393..8eba6b922b 100644 --- a/frontend/src/features/tabs/Inpainting/inpaintingSliceSelectors.ts +++ b/frontend/src/features/tabs/Inpainting/inpaintingSliceSelectors.ts @@ -4,6 +4,18 @@ import { RootState } from '../../../app/store'; import { OptionsState } from '../../options/optionsSlice'; import { tabMap } from '../InvokeTabs'; import { InpaintingState } from './inpaintingSlice'; +import { rgbaColorToRgbString } from './util/colorToString'; + +export const inpaintingCanvasLinesSelector = createSelector( + (state: RootState) => state.inpainting, + (inpainting: InpaintingState) => { + const { lines, maskColor } = inpainting; + return { + lines, + maskColorString: rgbaColorToRgbString(maskColor), + }; + } +); export const inpaintingControlsSelector = createSelector( [(state: RootState) => state.inpainting, (state: RootState) => state.options], @@ -12,7 +24,6 @@ export const inpaintingControlsSelector = createSelector( tool, brushSize, maskColor, - maskOpacity, shouldInvertMask, shouldShowMask, shouldShowCheckboardTransparency, @@ -20,6 +31,7 @@ export const inpaintingControlsSelector = createSelector( pastLines, futureLines, isMovingBoundingBox, + shouldShowBoundingBoxFill, } = inpainting; const { activeTab, showDualDisplay } = options; @@ -28,7 +40,6 @@ export const inpaintingControlsSelector = createSelector( tool, brushSize, maskColor, - maskOpacity, shouldInvertMask, shouldShowMask, shouldShowCheckboardTransparency, @@ -38,6 +49,7 @@ export const inpaintingControlsSelector = createSelector( isMovingBoundingBox, activeTabName: tabMap[activeTab], showDualDisplay, + shouldShowBoundingBoxFill, }; }, { @@ -58,13 +70,13 @@ export const inpaintingCanvasSelector = createSelector( shouldShowMask, shouldShowCheckboardTransparency, shouldShowBrushPreview, - maskOpacity, imageToInpaint, isMovingBoundingBox, boundingBoxDimensions, canvasDimensions, boundingBoxCoordinate, stageScale, + shouldShowBoundingBoxFill, } = inpainting; return { tool, @@ -74,13 +86,13 @@ export const inpaintingCanvasSelector = createSelector( shouldShowMask, shouldShowCheckboardTransparency, shouldShowBrushPreview, - maskOpacity, imageToInpaint, isMovingBoundingBox, boundingBoxDimensions, canvasDimensions, boundingBoxCoordinate, stageScale, + shouldShowBoundingBoxFill, }; }, { diff --git a/frontend/src/features/tabs/Inpainting/util/boundingBoxAnchorBoundDragFunc.ts b/frontend/src/features/tabs/Inpainting/util/boundingBoxAnchorBoundDragFunc.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/features/tabs/Inpainting/util/colorToString.ts b/frontend/src/features/tabs/Inpainting/util/colorToString.ts index 4cdddfa934..8d503f4cd1 100644 --- a/frontend/src/features/tabs/Inpainting/util/colorToString.ts +++ b/frontend/src/features/tabs/Inpainting/util/colorToString.ts @@ -5,6 +5,11 @@ export const rgbaColorToString = (color: RgbaColor): string => { return `rgba(${r}, ${g}, ${b}, ${a})`; }; +export const rgbaColorToRgbString = (color: RgbaColor): string => { + const { r, g, b } = color; + return `rgba(${r}, ${g}, ${b})`; +}; + export const rgbColorToString = (color: RgbColor): string => { const { r, g, b } = color; return `rgba(${r}, ${g}, ${b})`; diff --git a/frontend/src/features/tabs/Inpainting/util/constants.ts b/frontend/src/features/tabs/Inpainting/util/constants.ts index aa2cbf12ce..582f1007d2 100644 --- a/frontend/src/features/tabs/Inpainting/util/constants.ts +++ b/frontend/src/features/tabs/Inpainting/util/constants.ts @@ -3,3 +3,8 @@ export const DASH_WIDTH = 4; // speed of marching ants (lower is faster) export const MARCHING_ANTS_SPEED = 30; + +// bounding box anchor size +export const TRANSFORMER_ANCHOR_SIZE = 10; + +