Improves bounding box behavior

This commit is contained in:
psychedelicious 2022-10-28 13:59:52 +11:00
parent d035e0e811
commit 6f51b2078e
6 changed files with 268 additions and 234 deletions

View File

@ -133,7 +133,7 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
{ {
title: 'Quick Toggle Brush/Eraser', title: 'Quick Toggle Brush/Eraser',
desc: 'Quick toggle between brush and eraser', desc: 'Quick toggle between brush and eraser',
hotkey: 'Alt', hotkey: 'Z',
}, },
{ {
title: 'Decrease Brush Size', title: 'Decrease Brush Size',

View File

@ -16,10 +16,8 @@ import { useAppDispatch, useAppSelector } from '../../../app/store';
import { import {
addLine, addLine,
addPointToCurrentLine, addPointToCurrentLine,
setBoundingBoxCoordinate,
setCursorPosition, setCursorPosition,
setIsDrawing, setIsDrawing,
setIsMovingBoundingBox,
} from './inpaintingSlice'; } from './inpaintingSlice';
import { inpaintingCanvasSelector } from './inpaintingSliceSelectors'; import { inpaintingCanvasSelector } from './inpaintingSliceSelectors';
@ -30,7 +28,6 @@ import InpaintingCanvasBrushPreviewOutline from './components/InpaintingCanvasBr
import Cacher from './components/Cacher'; import Cacher from './components/Cacher';
import { Vector2d } from 'konva/lib/types'; import { Vector2d } from 'konva/lib/types';
import getScaledCursorPosition from './util/getScaledCursorPosition'; import getScaledCursorPosition from './util/getScaledCursorPosition';
import _ from 'lodash';
import InpaintingBoundingBoxPreview, { import InpaintingBoundingBoxPreview, {
InpaintingBoundingBoxPreviewOverlay, InpaintingBoundingBoxPreviewOverlay,
} from './components/InpaintingBoundingBoxPreview'; } from './components/InpaintingBoundingBoxPreview';
@ -53,14 +50,10 @@ const InpaintingCanvas = () => {
shouldShowCheckboardTransparency, shouldShowCheckboardTransparency,
maskColor, maskColor,
imageToInpaint, imageToInpaint,
isMovingBoundingBox,
boundingBoxDimensions,
canvasDimensions,
boundingBoxCoordinate,
stageScale, stageScale,
shouldShowBoundingBoxFill, shouldShowBoundingBoxFill,
isDrawing, isDrawing,
isTransformingBoundingBox, shouldLockBoundingBox,
shouldShowBoundingBox, shouldShowBoundingBox,
} = useAppSelector(inpaintingCanvasSelector); } = useAppSelector(inpaintingCanvasSelector);
@ -91,12 +84,22 @@ const InpaintingCanvas = () => {
} }
}, [imageToInpaint, dispatch, stageScale]); }, [imageToInpaint, dispatch, stageScale]);
/**
*
* Canvas onMouseDown
*
*/
const handleMouseDown = useCallback(() => { const handleMouseDown = useCallback(() => {
if (!stageRef.current) return; if (!stageRef.current) return;
const scaledCursorPosition = getScaledCursorPosition(stageRef.current); const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
if (!scaledCursorPosition || !maskLayerRef.current) return; if (
!scaledCursorPosition ||
!maskLayerRef.current ||
!shouldLockBoundingBox
)
return;
dispatch(setIsDrawing(true)); dispatch(setIsDrawing(true));
@ -108,69 +111,55 @@ const InpaintingCanvas = () => {
points: [scaledCursorPosition.x, scaledCursorPosition.y], points: [scaledCursorPosition.x, scaledCursorPosition.y],
}) })
); );
}, [dispatch, brushSize, tool]); }, [dispatch, brushSize, tool, shouldLockBoundingBox]);
const handleMouseMove = useCallback(() => { /**
if (!stageRef.current) return; *
* Canvas onMouseMove
*
*/
const handleMouseMove = useCallback(
() => {
if (!stageRef.current) return;
const scaledCursorPosition = getScaledCursorPosition(stageRef.current); const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
if (!scaledCursorPosition) return; if (!scaledCursorPosition) return;
dispatch(setCursorPosition(scaledCursorPosition)); dispatch(setCursorPosition(scaledCursorPosition));
if (!maskLayerRef.current) { if (!maskLayerRef.current || !shouldLockBoundingBox) {
return; return;
} }
const deltaX = lastCursorPosition.current.x - scaledCursorPosition.x; lastCursorPosition.current = scaledCursorPosition;
const deltaY = lastCursorPosition.current.y - scaledCursorPosition.y;
lastCursorPosition.current = scaledCursorPosition; if (!isDrawing) return;
if (isMovingBoundingBox) {
const x = _.clamp(
Math.floor(boundingBoxCoordinate.x - deltaX),
0,
canvasDimensions.width - boundingBoxDimensions.width
);
const y = _.clamp(
Math.floor(boundingBoxCoordinate.y - deltaY),
0,
canvasDimensions.height - boundingBoxDimensions.height
);
didMouseMoveRef.current = true;
// Extend the current line
dispatch( dispatch(
setBoundingBoxCoordinate({ addPointToCurrentLine([scaledCursorPosition.x, scaledCursorPosition.y])
x,
y,
})
); );
},
[dispatch, isDrawing, shouldLockBoundingBox]
);
return; /**
} *
if (!isDrawing) return; * Canvas onMouseUp
*
didMouseMoveRef.current = true; */
// Extend the current line
dispatch(
addPointToCurrentLine([scaledCursorPosition.x, scaledCursorPosition.y])
);
}, [
dispatch,
isMovingBoundingBox,
boundingBoxDimensions,
canvasDimensions,
boundingBoxCoordinate,
isDrawing,
]);
const handleMouseUp = useCallback(() => { const handleMouseUp = useCallback(() => {
if (!didMouseMoveRef.current && isDrawing && stageRef.current) { if (!didMouseMoveRef.current && isDrawing && stageRef.current) {
const scaledCursorPosition = getScaledCursorPosition(stageRef.current); const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
if (!scaledCursorPosition || !maskLayerRef.current) return; if (
!scaledCursorPosition ||
!maskLayerRef.current ||
!shouldLockBoundingBox
)
return;
/** /**
* Extend the current line. * Extend the current line.
@ -185,13 +174,23 @@ const InpaintingCanvas = () => {
didMouseMoveRef.current = false; didMouseMoveRef.current = false;
} }
dispatch(setIsDrawing(false)); dispatch(setIsDrawing(false));
}, [dispatch, isDrawing]); }, [dispatch, isDrawing, shouldLockBoundingBox]);
/**
*
* Canvas onMouseOut
*
*/
const handleMouseOutCanvas = useCallback(() => { const handleMouseOutCanvas = useCallback(() => {
dispatch(setCursorPosition(null)); dispatch(setCursorPosition(null));
dispatch(setIsDrawing(false)); dispatch(setIsDrawing(false));
}, [dispatch]); }, [dispatch]);
/**
*
* Canvas onMouseEnter
*
*/
const handleMouseEnter = useCallback( const handleMouseEnter = useCallback(
(e: KonvaEventObject<MouseEvent>) => { (e: KonvaEventObject<MouseEvent>) => {
if (e.evt.buttons === 1) { if (e.evt.buttons === 1) {
@ -202,8 +201,7 @@ const InpaintingCanvas = () => {
if ( if (
!scaledCursorPosition || !scaledCursorPosition ||
!maskLayerRef.current || !maskLayerRef.current ||
isMovingBoundingBox || !shouldLockBoundingBox
isTransformingBoundingBox
) )
return; return;
@ -219,7 +217,7 @@ const InpaintingCanvas = () => {
); );
} }
}, },
[dispatch, brushSize, tool, isMovingBoundingBox, isTransformingBoundingBox] [dispatch, brushSize, tool, shouldLockBoundingBox]
); );
return ( return (

View File

@ -1,9 +1,10 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import Konva from 'konva'; import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node'; import { KonvaEventObject } from 'konva/lib/Node';
import { Box } from 'konva/lib/shapes/Transformer';
import { Vector2d } from 'konva/lib/types'; import { Vector2d } from 'konva/lib/types';
import _ from 'lodash'; import _ from 'lodash';
import { useEffect, useLayoutEffect, useRef } from 'react'; import { useCallback, useEffect, useRef } from 'react';
import { Group, Rect, Transformer } from 'react-konva'; import { Group, Rect, Transformer } from 'react-konva';
import { import {
RootState, RootState,
@ -15,9 +16,6 @@ import {
InpaintingState, InpaintingState,
setBoundingBoxCoordinate, setBoundingBoxCoordinate,
setBoundingBoxDimensions, setBoundingBoxDimensions,
setIsTransformingBoundingBox,
setIsDrawing,
setShouldShowBrush,
} from '../inpaintingSlice'; } from '../inpaintingSlice';
import { rgbaColorToString } from '../util/colorToString'; import { rgbaColorToString } from '../util/colorToString';
import { import {
@ -35,7 +33,6 @@ const boundingBoxPreviewSelector = createSelector(
canvasDimensions, canvasDimensions,
stageScale, stageScale,
imageToInpaint, imageToInpaint,
isMovingBoundingBox,
shouldLockBoundingBox, shouldLockBoundingBox,
} = inpainting; } = inpainting;
return { return {
@ -47,7 +44,6 @@ const boundingBoxPreviewSelector = createSelector(
imageToInpaint, imageToInpaint,
dash: DASH_WIDTH / stageScale, // scale dash lengths dash: DASH_WIDTH / stageScale, // scale dash lengths
strokeWidth: 1 / stageScale, // scale stroke thickness strokeWidth: 1 / stageScale, // scale stroke thickness
isMovingBoundingBox,
shouldLockBoundingBox, shouldLockBoundingBox,
}; };
}, },
@ -173,7 +169,150 @@ const InpaintingBoundingBoxPreview = () => {
const scaledStep = 64 * stageScale; const scaledStep = 64 * stageScale;
console.log(shouldLockBoundingBox); const handleOnDragMove = useCallback(
(e: KonvaEventObject<DragEvent>) => {
dispatch(setBoundingBoxCoordinate({ x: e.target.x(), y: e.target.y() }));
},
[dispatch]
);
const dragBoundFunc = useCallback(
(position: Vector2d) => {
if (!imageToInpaint) return boundingBoxCoordinate;
const { x, y } = position;
const maxX = imageToInpaint.width - boundingBoxDimensions.width;
const maxY = imageToInpaint.height - boundingBoxDimensions.height;
const clampedX = _.clamp(x, 0, maxX);
const clampedY = _.clamp(y, 0, maxY);
return { x: clampedX, y: clampedY };
},
[boundingBoxCoordinate, boundingBoxDimensions, imageToInpaint]
);
const handleOnTransform = useCallback(() => {
/**
* 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);
}, [dispatch]);
const anchorDragBoundFunc = useCallback(
(
oldPos: Vector2d, // old absolute position of anchor point
newPos: Vector2d, // new absolute position (potentially) of anchor point
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_e: MouseEvent
) => {
/**
* 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.
*/
// 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;
},
[scaledStep]
);
const boundBoxFunc = useCallback(
(oldBoundBox: Box, newBoundBox: Box) => {
/**
* 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 * stageScale ||
newBoundBox.height + newBoundBox.y >
imageToInpaint.height * stageScale ||
newBoundBox.x < 0 ||
newBoundBox.y < 0
) {
return oldBoundBox;
}
return newBoundBox;
},
[imageToInpaint, stageScale]
);
return ( return (
<> <>
@ -185,150 +324,40 @@ const InpaintingBoundingBoxPreview = () => {
ref={shapeRef} ref={shapeRef}
stroke={'white'} stroke={'white'}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
listening={false} listening={!shouldLockBoundingBox}
onTransformStart={() => { onMouseEnter={(e) => {
dispatch(setIsDrawing(false)); // style stage container:
dispatch(setShouldShowBrush(false)); const container = e?.target?.getStage()?.container();
dispatch(setIsTransformingBoundingBox(true)); if (!container) return;
container.style.cursor = 'move';
}} }}
onTransformEnd={() => { onMouseLeave={(e) => {
dispatch(setShouldShowBrush(true)); const container = e?.target?.getStage()?.container();
dispatch(setIsTransformingBoundingBox(false)); if (!container) return;
}} container.style.cursor = 'default';
onTransform={() => {
/**
* 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);
}} }}
draggable={!shouldLockBoundingBox}
onDragMove={handleOnDragMove}
dragBoundFunc={dragBoundFunc}
onTransform={handleOnTransform}
/> />
<Transformer <Transformer
enabledAnchors={shouldLockBoundingBox ? [] : undefined}
ref={transformerRef} ref={transformerRef}
rotateEnabled={false}
anchorSize={15}
anchorFill={'rgba(212,216,234,1)'}
anchorStroke={'rgb(42,42,42)'}
borderEnabled={true}
borderStroke={'black'}
borderDash={[4, 4]}
anchorCornerRadius={3} anchorCornerRadius={3}
anchorFill={'rgba(212,216,234,1)'}
anchorSize={15}
anchorStroke={'rgb(42,42,42)'}
borderDash={[4, 4]}
borderStroke={'black'}
rotateEnabled={false}
borderEnabled={true}
flipEnabled={false}
ignoreStroke={true} ignoreStroke={true}
keepRatio={false} keepRatio={false}
flipEnabled={false} listening={!shouldLockBoundingBox}
onMouseDown={(e: KonvaEventObject<MouseEvent>) => { enabledAnchors={shouldLockBoundingBox ? [] : undefined}
e.cancelBubble = true; boundBoxFunc={boundBoxFunc}
}} anchorDragBoundFunc={anchorDragBoundFunc}
onMouseOver={(e: KonvaEventObject<MouseEvent>) => {
e.cancelBubble = true;
}}
anchorDragBoundFunc={(
oldPos: Vector2d, // old absolute position of anchor point
newPos: Vector2d, // new absolute position (potentially) of anchor point
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_e: MouseEvent
) => {
/**
* 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.
*/
// 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 * stageScale ||
newBoundBox.height + newBoundBox.y >
imageToInpaint.height * stageScale ||
newBoundBox.x < 0 ||
newBoundBox.y < 0
) {
return oldBoundBox;
}
return newBoundBox;
}}
/> />
</> </>
); );

View File

@ -10,7 +10,9 @@ import { OptionsState } from '../../../options/optionsSlice';
import { tabMap } from '../../InvokeTabs'; import { tabMap } from '../../InvokeTabs';
import { import {
InpaintingState, InpaintingState,
setIsMovingBoundingBox, setIsDrawing,
setShouldLockBoundingBox,
setShouldShowBrush,
toggleTool, toggleTool,
} from '../inpaintingSlice'; } from '../inpaintingSlice';
@ -20,14 +22,12 @@ const keyboardEventManagerSelector = createSelector(
const { const {
shouldShowMask, shouldShowMask,
cursorPosition, cursorPosition,
isMovingBoundingBox,
shouldLockBoundingBox, shouldLockBoundingBox,
} = inpainting; } = inpainting;
return { return {
activeTabName: tabMap[options.activeTab], activeTabName: tabMap[options.activeTab],
shouldShowMask, shouldShowMask,
isCursorOnCanvas: Boolean(cursorPosition), isCursorOnCanvas: Boolean(cursorPosition),
isMovingBoundingBox,
shouldLockBoundingBox, shouldLockBoundingBox,
}; };
}, },
@ -44,7 +44,6 @@ const KeyboardEventManager = () => {
shouldShowMask, shouldShowMask,
activeTabName, activeTabName,
isCursorOnCanvas, isCursorOnCanvas,
isMovingBoundingBox,
shouldLockBoundingBox, shouldLockBoundingBox,
} = useAppSelector(keyboardEventManagerSelector); } = useAppSelector(keyboardEventManagerSelector);
@ -54,11 +53,9 @@ const KeyboardEventManager = () => {
useEffect(() => { useEffect(() => {
const listener = (e: KeyboardEvent) => { const listener = (e: KeyboardEvent) => {
if ( if (
!['Alt', ' '].includes(e.key) || !['z', ' '].includes(e.key) ||
activeTabName !== 'inpainting' || activeTabName !== 'inpainting' ||
!shouldShowMask || !shouldShowMask
e.repeat ||
shouldLockBoundingBox
) { ) {
return; return;
} }
@ -72,8 +69,10 @@ const KeyboardEventManager = () => {
wasLastEventOverCanvas.current = false; wasLastEventOverCanvas.current = false;
return; return;
} }
e.stopPropagation();
// cursor is over canvas e.preventDefault();
if (e.repeat) return;
// cursor is over canvas, we can preventDefault now
// if this is the first event // if this is the first event
if (!lastEvent.current) { if (!lastEvent.current) {
@ -87,15 +86,20 @@ const KeyboardEventManager = () => {
return; return;
} }
e.preventDefault();
switch (e.key) { switch (e.key) {
case 'Alt': { case 'z': {
dispatch(toggleTool()); dispatch(toggleTool());
break; break;
} }
case ' ': { case ' ': {
dispatch(setIsMovingBoundingBox(e.type === 'keydown' ? true : false)); if (e.type === 'keydown') {
dispatch(setIsDrawing(false));
dispatch(setShouldLockBoundingBox(false));
dispatch(setShouldShowBrush(false));
} else {
dispatch(setShouldLockBoundingBox(true));
dispatch(setShouldShowBrush(true));
}
break; break;
} }
} }
@ -116,7 +120,7 @@ const KeyboardEventManager = () => {
activeTabName, activeTabName,
shouldShowMask, shouldShowMask,
isCursorOnCanvas, isCursorOnCanvas,
isMovingBoundingBox, shouldLockBoundingBox,
]); ]);
return null; return null;

View File

@ -291,7 +291,19 @@ export const inpaintingSlice = createSlice({
}; };
}, },
setBoundingBoxCoordinate: (state, action: PayloadAction<Vector2d>) => { setBoundingBoxCoordinate: (state, action: PayloadAction<Vector2d>) => {
state.boundingBoxCoordinate = action.payload; state.boundingBoxCoordinate = action.payload
// const { x, y } = action.payload;
// const maxX =
// state.canvasDimensions.width - state.boundingBoxDimensions.width;
// const maxY =
// state.canvasDimensions.height - state.boundingBoxDimensions.height;
// const clampedX = _.clamp(x, 0, maxX);
// const clampedY = _.clamp(y, 0, maxY);
// state.boundingBoxCoordinate = { x: clampedX, y: clampedY };
}, },
setIsMovingBoundingBox: (state, action: PayloadAction<boolean>) => { setIsMovingBoundingBox: (state, action: PayloadAction<boolean>) => {
state.isMovingBoundingBox = action.payload; state.isMovingBoundingBox = action.payload;

View File

@ -69,35 +69,26 @@ export const inpaintingCanvasSelector = createSelector(
shouldInvertMask, shouldInvertMask,
shouldShowMask, shouldShowMask,
shouldShowCheckboardTransparency, shouldShowCheckboardTransparency,
shouldShowBrushPreview,
imageToInpaint, imageToInpaint,
isMovingBoundingBox,
boundingBoxDimensions,
canvasDimensions,
boundingBoxCoordinate,
stageScale, stageScale,
shouldShowBoundingBoxFill, shouldShowBoundingBoxFill,
isDrawing, isDrawing,
isTransformingBoundingBox, shouldLockBoundingBox,
shouldShowBoundingBox, shouldShowBoundingBox,
} = inpainting; } = inpainting;
return { return {
tool, tool,
brushSize, brushSize,
maskColor,
shouldInvertMask, shouldInvertMask,
shouldShowMask, shouldShowMask,
shouldShowCheckboardTransparency, shouldShowCheckboardTransparency,
shouldShowBrushPreview, maskColor,
imageToInpaint, imageToInpaint,
isMovingBoundingBox,
boundingBoxDimensions,
canvasDimensions,
boundingBoxCoordinate,
stageScale, stageScale,
shouldShowBoundingBoxFill, shouldShowBoundingBoxFill,
isDrawing, isDrawing,
isTransformingBoundingBox, shouldLockBoundingBox,
shouldShowBoundingBox, shouldShowBoundingBox,
}; };
}, },