Fixes edge cases with bounding box

This commit is contained in:
psychedelicious 2022-10-27 18:33:34 +11:00
parent 9e2ce00f7b
commit 65acdfb09b
8 changed files with 98 additions and 29 deletions

View File

@ -18,6 +18,7 @@ import {
addPointToCurrentLine, addPointToCurrentLine,
setBoundingBoxCoordinate, setBoundingBoxCoordinate,
setCursorPosition, setCursorPosition,
setIsDrawing,
setIsMovingBoundingBox, setIsMovingBoundingBox,
} from './inpaintingSlice'; } from './inpaintingSlice';
import { inpaintingCanvasSelector } from './inpaintingSliceSelectors'; import { inpaintingCanvasSelector } from './inpaintingSliceSelectors';
@ -58,6 +59,8 @@ const InpaintingCanvas = () => {
boundingBoxCoordinate, boundingBoxCoordinate,
stageScale, stageScale,
shouldShowBoundingBoxFill, shouldShowBoundingBoxFill,
isDrawing,
isBoundingBoxTransforming,
} = useAppSelector(inpaintingCanvasSelector); } = useAppSelector(inpaintingCanvasSelector);
// set the closure'd refs // set the closure'd refs
@ -69,7 +72,6 @@ const InpaintingCanvas = () => {
// Use refs for values that do not affect rendering, other values in redux // Use refs for values that do not affect rendering, other values in redux
const didMouseMoveRef = useRef<boolean>(false); const didMouseMoveRef = useRef<boolean>(false);
const isDrawing = useRef<boolean>(false);
// Load the image into this // Load the image into this
const [canvasBgImage, setCanvasBgImage] = useState<HTMLImageElement | null>( const [canvasBgImage, setCanvasBgImage] = useState<HTMLImageElement | null>(
@ -95,7 +97,7 @@ const InpaintingCanvas = () => {
if (!scaledCursorPosition || !maskLayerRef.current) return; if (!scaledCursorPosition || !maskLayerRef.current) return;
isDrawing.current = true; dispatch(setIsDrawing(true));
// Add a new line starting from the current cursor position. // Add a new line starting from the current cursor position.
dispatch( dispatch(
@ -147,8 +149,7 @@ const InpaintingCanvas = () => {
return; return;
} }
if (!isDrawing) return;
if (!isDrawing.current) return;
didMouseMoveRef.current = true; didMouseMoveRef.current = true;
// Extend the current line // Extend the current line
@ -161,10 +162,11 @@ const InpaintingCanvas = () => {
boundingBoxDimensions, boundingBoxDimensions,
canvasDimensions, canvasDimensions,
boundingBoxCoordinate, boundingBoxCoordinate,
isDrawing,
]); ]);
const handleMouseUp = useCallback(() => { const handleMouseUp = useCallback(() => {
if (!didMouseMoveRef.current && isDrawing.current && 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) return;
@ -181,13 +183,13 @@ const InpaintingCanvas = () => {
} else { } else {
didMouseMoveRef.current = false; didMouseMoveRef.current = false;
} }
isDrawing.current = false; dispatch(setIsDrawing(false));
}, [dispatch]); }, [dispatch, isDrawing]);
const handleMouseOutCanvas = useCallback(() => { const handleMouseOutCanvas = useCallback(() => {
dispatch(setCursorPosition(null)); dispatch(setCursorPosition(null));
dispatch(setIsMovingBoundingBox(false)); dispatch(setIsMovingBoundingBox(false));
isDrawing.current = false; dispatch(setIsDrawing(false));
}, [dispatch]); }, [dispatch]);
const handleMouseEnter = useCallback( const handleMouseEnter = useCallback(
@ -197,9 +199,15 @@ const InpaintingCanvas = () => {
const scaledCursorPosition = getScaledCursorPosition(stageRef.current); const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
if (!scaledCursorPosition || !maskLayerRef.current) return; if (
!scaledCursorPosition ||
!maskLayerRef.current ||
isMovingBoundingBox ||
isBoundingBoxTransforming
)
return;
isDrawing.current = true; dispatch(setIsDrawing(true));
// Add a new line starting from the current cursor position. // Add a new line starting from the current cursor position.
dispatch( dispatch(
@ -211,7 +219,7 @@ const InpaintingCanvas = () => {
); );
} }
}, },
[dispatch, brushSize, tool] [dispatch, brushSize, tool, isMovingBoundingBox, isBoundingBoxTransforming]
); );
return ( return (

View File

@ -25,6 +25,7 @@ const Cacher = () => {
shouldShowBrushPreview, shouldShowBrushPreview,
shouldShowCheckboardTransparency, shouldShowCheckboardTransparency,
imageToInpaint, imageToInpaint,
shouldShowBrush,
} = useAppSelector((state: RootState) => state.inpainting); } = useAppSelector((state: RootState) => state.inpainting);
useLayoutEffect(() => { useLayoutEffect(() => {
@ -48,6 +49,7 @@ const Cacher = () => {
shouldShowBrushPreview, shouldShowBrushPreview,
shouldShowCheckboardTransparency, shouldShowCheckboardTransparency,
imageToInpaint, imageToInpaint,
shouldShowBrush,
]); ]);
return null; return null;

View File

@ -1,5 +1,6 @@
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 { Vector2d } from 'konva/lib/types'; import { Vector2d } from 'konva/lib/types';
import _ from 'lodash'; import _ from 'lodash';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
@ -14,6 +15,10 @@ import {
InpaintingState, InpaintingState,
setBoundingBoxCoordinate, setBoundingBoxCoordinate,
setBoundingBoxDimensions, setBoundingBoxDimensions,
setIsBoundingBoxTransforming,
setIsDrawing,
setIsMovingBoundingBox,
setShouldShowBrush,
} from '../inpaintingSlice'; } from '../inpaintingSlice';
import { rgbaColorToString } from '../util/colorToString'; import { rgbaColorToString } from '../util/colorToString';
import { import {
@ -32,6 +37,7 @@ const boundingBoxPreviewSelector = createSelector(
canvasDimensions, canvasDimensions,
stageScale, stageScale,
imageToInpaint, imageToInpaint,
isMovingBoundingBox,
} = inpainting; } = inpainting;
return { return {
boundingBoxCoordinate, boundingBoxCoordinate,
@ -43,6 +49,7 @@ const boundingBoxPreviewSelector = createSelector(
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
anchorSize: TRANSFORMER_ANCHOR_SIZE, anchorSize: TRANSFORMER_ANCHOR_SIZE,
isMovingBoundingBox,
}; };
}, },
{ {
@ -154,6 +161,7 @@ const InpaintingBoundingBoxPreview = () => {
anchorSize, anchorSize,
stageScale, stageScale,
imageToInpaint, imageToInpaint,
isMovingBoundingBox,
} = useAppSelector(boundingBoxPreviewSelector); } = useAppSelector(boundingBoxPreviewSelector);
const transformerRef = useRef<Konva.Transformer>(null); const transformerRef = useRef<Konva.Transformer>(null);
@ -176,12 +184,22 @@ const InpaintingBoundingBoxPreview = () => {
stroke={'white'} stroke={'white'}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
listening={false} listening={false}
onTransformStart={() => {
dispatch(setIsDrawing(false));
dispatch(setShouldShowBrush(false));
dispatch(setIsBoundingBoxTransforming(true));
}}
onTransformEnd={() => {
dispatch(setShouldShowBrush(true));
dispatch(setIsBoundingBoxTransforming(false));
}}
onTransform={() => { onTransform={() => {
/** /**
* The Konva Transformer changes the object's anchor point and scale factor, * 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 * not its width and height. We need to un-scale the width and height before
* setting the values. * setting the values.
*/ */
console.log(isMovingBoundingBox)
if (!shapeRef.current) return; if (!shapeRef.current) return;
const rect = shapeRef.current; const rect = shapeRef.current;
@ -227,11 +245,14 @@ const InpaintingBoundingBoxPreview = () => {
ignoreStroke={true} ignoreStroke={true}
keepRatio={false} keepRatio={false}
flipEnabled={false} flipEnabled={false}
onMouseDown={(e: KonvaEventObject<MouseEvent>) => {
e.cancelBubble = true;
}}
anchorDragBoundFunc={( anchorDragBoundFunc={(
oldPos: Vector2d, // old absolute position of anchor point oldPos: Vector2d, // old absolute position of anchor point
newPos: Vector2d, // new absolute position (potentially) of anchor point newPos: Vector2d, // new absolute position (potentially) of anchor point
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
_event: MouseEvent _e: MouseEvent
) => { ) => {
/** /**
* Konva does not transform with width or height. It transforms the anchor point * Konva does not transform with width or height. It transforms the anchor point

View File

@ -15,6 +15,7 @@ const inpaintingCanvasBrushPreviewSelector = createSelector(
brushSize, brushSize,
maskColor, maskColor,
tool, tool,
shouldShowBrush,
} = inpainting; } = inpainting;
return { return {
@ -25,6 +26,7 @@ const inpaintingCanvasBrushPreviewSelector = createSelector(
brushSize, brushSize,
maskColorString: rgbaColorToRgbString(maskColor), maskColorString: rgbaColorToRgbString(maskColor),
tool, tool,
shouldShowBrush,
}; };
}, },
{ {
@ -46,9 +48,12 @@ const InpaintingCanvasBrushPreview = () => {
brushSize, brushSize,
maskColorString, maskColorString,
tool, tool,
shouldShowBrush,
} = useAppSelector(inpaintingCanvasBrushPreviewSelector); } = useAppSelector(inpaintingCanvasBrushPreviewSelector);
if (!(cursorPosition || shouldShowBrushPreview)) return null; if (!shouldShowBrush || !(cursorPosition || shouldShowBrushPreview)) {
return null;
}
return ( return (
<Circle <Circle

View File

@ -13,6 +13,7 @@ const inpaintingCanvasBrushPreviewSelector = createSelector(
shouldShowBrushPreview, shouldShowBrushPreview,
brushSize, brushSize,
stageScale, stageScale,
shouldShowBrush,
} = inpainting; } = inpainting;
return { return {
@ -22,6 +23,7 @@ const inpaintingCanvasBrushPreviewSelector = createSelector(
shouldShowBrushPreview, shouldShowBrushPreview,
brushSize, brushSize,
strokeWidth: 1 / stageScale, // scale stroke thickness strokeWidth: 1 / stageScale, // scale stroke thickness
shouldShowBrush,
}; };
}, },
{ {
@ -42,12 +44,14 @@ const InpaintingCanvasBrushPreviewOutline = () => {
shouldShowBrushPreview, shouldShowBrushPreview,
brushSize, brushSize,
strokeWidth, strokeWidth,
shouldShowBrush,
} = useAppSelector(inpaintingCanvasBrushPreviewSelector); } = useAppSelector(inpaintingCanvasBrushPreviewSelector);
if (!((cursorPosition || shouldShowBrushPreview) && width && height)) if (!shouldShowBrush || !(cursorPosition || shouldShowBrushPreview))
return null; return null;
return ( return (
<>
<Circle <Circle
x={cursorPosition ? cursorPosition.x : width / 2} x={cursorPosition ? cursorPosition.x : width / 2}
y={cursorPosition ? cursorPosition.y : height / 2} y={cursorPosition ? cursorPosition.y : height / 2}
@ -57,6 +61,14 @@ const InpaintingCanvasBrushPreviewOutline = () => {
strokeEnabled={true} strokeEnabled={true}
listening={false} listening={false}
/> />
<Circle
x={cursorPosition ? cursorPosition.x : width / 2}
y={cursorPosition ? cursorPosition.y : height / 2}
radius={1}
fill={'rgba(0,0,0,1)'}
listening={false}
/>
</>
); );
}; };
export default InpaintingCanvasBrushPreviewOutline; export default InpaintingCanvasBrushPreviewOutline;

View File

@ -10,6 +10,7 @@ import { OptionsState } from '../../../options/optionsSlice';
import { tabMap } from '../../InvokeTabs'; import { tabMap } from '../../InvokeTabs';
import { import {
InpaintingState, InpaintingState,
setIsMovingBoundingBox,
toggleIsMovingBoundingBox, toggleIsMovingBoundingBox,
toggleTool, toggleTool,
} from '../inpaintingSlice'; } from '../inpaintingSlice';
@ -17,11 +18,12 @@ import {
const keyboardEventManagerSelector = createSelector( const keyboardEventManagerSelector = createSelector(
[(state: RootState) => state.options, (state: RootState) => state.inpainting], [(state: RootState) => state.options, (state: RootState) => state.inpainting],
(options: OptionsState, inpainting: InpaintingState) => { (options: OptionsState, inpainting: InpaintingState) => {
const { shouldShowMask, cursorPosition } = inpainting; const { shouldShowMask, cursorPosition, isMovingBoundingBox } = inpainting;
return { return {
activeTabName: tabMap[options.activeTab], activeTabName: tabMap[options.activeTab],
shouldShowMask, shouldShowMask,
isCursorOnCanvas: Boolean(cursorPosition), isCursorOnCanvas: Boolean(cursorPosition),
isMovingBoundingBox,
}; };
}, },
{ {
@ -33,9 +35,12 @@ const keyboardEventManagerSelector = createSelector(
const KeyboardEventManager = () => { const KeyboardEventManager = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { shouldShowMask, activeTabName, isCursorOnCanvas } = useAppSelector( const {
keyboardEventManagerSelector shouldShowMask,
); activeTabName,
isCursorOnCanvas,
isMovingBoundingBox,
} = useAppSelector(keyboardEventManagerSelector);
const isFirstEvent = useRef<boolean>(true); const isFirstEvent = useRef<boolean>(true);
const wasLastEventOverCanvas = useRef<boolean>(false); const wasLastEventOverCanvas = useRef<boolean>(false);
@ -84,7 +89,7 @@ const KeyboardEventManager = () => {
break; break;
} }
case ' ': { case ' ': {
dispatch(toggleIsMovingBoundingBox()); dispatch(setIsMovingBoundingBox(e.type === 'keydown' ? true : false));
break; break;
} }
} }
@ -100,7 +105,13 @@ const KeyboardEventManager = () => {
document.removeEventListener('keydown', listener); document.removeEventListener('keydown', listener);
document.removeEventListener('keyup', listener); document.removeEventListener('keyup', listener);
}; };
}, [dispatch, activeTabName, shouldShowMask, isCursorOnCanvas]); }, [
dispatch,
activeTabName,
shouldShowMask,
isCursorOnCanvas,
isMovingBoundingBox,
]);
return null; return null;
}; };

View File

@ -46,6 +46,7 @@ export interface InpaintingState {
shouldShowMask: boolean; shouldShowMask: boolean;
shouldInvertMask: boolean; shouldInvertMask: boolean;
shouldShowCheckboardTransparency: boolean; shouldShowCheckboardTransparency: boolean;
shouldShowBrush: boolean;
shouldShowBrushPreview: boolean; shouldShowBrushPreview: boolean;
imageToInpaint?: InvokeAI.Image; imageToInpaint?: InvokeAI.Image;
needsRepaint: boolean; needsRepaint: boolean;
@ -70,6 +71,7 @@ const initialInpaintingState: InpaintingState = {
shouldShowMask: true, shouldShowMask: true,
shouldInvertMask: false, shouldInvertMask: false,
shouldShowCheckboardTransparency: false, shouldShowCheckboardTransparency: false,
shouldShowBrush: true,
shouldShowBrushPreview: false, shouldShowBrushPreview: false,
isMovingBoundingBox: false, isMovingBoundingBox: false,
needsRepaint: false, needsRepaint: false,
@ -144,6 +146,9 @@ export const inpaintingSlice = createSlice({
setShouldShowBrushPreview: (state, action: PayloadAction<boolean>) => { setShouldShowBrushPreview: (state, action: PayloadAction<boolean>) => {
state.shouldShowBrushPreview = action.payload; state.shouldShowBrushPreview = action.payload;
}, },
setShouldShowBrush: (state, action: PayloadAction<boolean>) => {
state.shouldShowBrush = action.payload;
},
setMaskColor: (state, action: PayloadAction<RgbaColor>) => { setMaskColor: (state, action: PayloadAction<RgbaColor>) => {
state.maskColor = action.payload; state.maskColor = action.payload;
}, },
@ -335,6 +340,7 @@ export const {
setShouldShowBoundingBoxFill, setShouldShowBoundingBoxFill,
setIsBoundingBoxTransforming, setIsBoundingBoxTransforming,
setIsDrawing, setIsDrawing,
setShouldShowBrush
} = inpaintingSlice.actions; } = inpaintingSlice.actions;
export default inpaintingSlice.reducer; export default inpaintingSlice.reducer;

View File

@ -77,6 +77,8 @@ export const inpaintingCanvasSelector = createSelector(
boundingBoxCoordinate, boundingBoxCoordinate,
stageScale, stageScale,
shouldShowBoundingBoxFill, shouldShowBoundingBoxFill,
isDrawing,
isBoundingBoxTransforming,
} = inpainting; } = inpainting;
return { return {
tool, tool,
@ -93,6 +95,8 @@ export const inpaintingCanvasSelector = createSelector(
boundingBoxCoordinate, boundingBoxCoordinate,
stageScale, stageScale,
shouldShowBoundingBoxFill, shouldShowBoundingBoxFill,
isDrawing,
isBoundingBoxTransforming,
}; };
}, },
{ {