mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Improves bounding box behavior
This commit is contained in:
parent
bbe53841e4
commit
27ba91e74d
@ -133,7 +133,7 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
|
||||
{
|
||||
title: 'Quick Toggle Brush/Eraser',
|
||||
desc: 'Quick toggle between brush and eraser',
|
||||
hotkey: 'Alt',
|
||||
hotkey: 'Z',
|
||||
},
|
||||
{
|
||||
title: 'Decrease Brush Size',
|
||||
|
@ -16,10 +16,8 @@ import { useAppDispatch, useAppSelector } from '../../../app/store';
|
||||
import {
|
||||
addLine,
|
||||
addPointToCurrentLine,
|
||||
setBoundingBoxCoordinate,
|
||||
setCursorPosition,
|
||||
setIsDrawing,
|
||||
setIsMovingBoundingBox,
|
||||
} from './inpaintingSlice';
|
||||
import { inpaintingCanvasSelector } from './inpaintingSliceSelectors';
|
||||
|
||||
@ -30,7 +28,6 @@ import InpaintingCanvasBrushPreviewOutline from './components/InpaintingCanvasBr
|
||||
import Cacher from './components/Cacher';
|
||||
import { Vector2d } from 'konva/lib/types';
|
||||
import getScaledCursorPosition from './util/getScaledCursorPosition';
|
||||
import _ from 'lodash';
|
||||
import InpaintingBoundingBoxPreview, {
|
||||
InpaintingBoundingBoxPreviewOverlay,
|
||||
} from './components/InpaintingBoundingBoxPreview';
|
||||
@ -53,14 +50,10 @@ const InpaintingCanvas = () => {
|
||||
shouldShowCheckboardTransparency,
|
||||
maskColor,
|
||||
imageToInpaint,
|
||||
isMovingBoundingBox,
|
||||
boundingBoxDimensions,
|
||||
canvasDimensions,
|
||||
boundingBoxCoordinate,
|
||||
stageScale,
|
||||
shouldShowBoundingBoxFill,
|
||||
isDrawing,
|
||||
isTransformingBoundingBox,
|
||||
shouldLockBoundingBox,
|
||||
shouldShowBoundingBox,
|
||||
} = useAppSelector(inpaintingCanvasSelector);
|
||||
|
||||
@ -91,12 +84,22 @@ const InpaintingCanvas = () => {
|
||||
}
|
||||
}, [imageToInpaint, dispatch, stageScale]);
|
||||
|
||||
/**
|
||||
*
|
||||
* Canvas onMouseDown
|
||||
*
|
||||
*/
|
||||
const handleMouseDown = useCallback(() => {
|
||||
if (!stageRef.current) return;
|
||||
|
||||
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
|
||||
|
||||
if (!scaledCursorPosition || !maskLayerRef.current) return;
|
||||
if (
|
||||
!scaledCursorPosition ||
|
||||
!maskLayerRef.current ||
|
||||
!shouldLockBoundingBox
|
||||
)
|
||||
return;
|
||||
|
||||
dispatch(setIsDrawing(true));
|
||||
|
||||
@ -108,9 +111,15 @@ const InpaintingCanvas = () => {
|
||||
points: [scaledCursorPosition.x, scaledCursorPosition.y],
|
||||
})
|
||||
);
|
||||
}, [dispatch, brushSize, tool]);
|
||||
}, [dispatch, brushSize, tool, shouldLockBoundingBox]);
|
||||
|
||||
const handleMouseMove = useCallback(() => {
|
||||
/**
|
||||
*
|
||||
* Canvas onMouseMove
|
||||
*
|
||||
*/
|
||||
const handleMouseMove = useCallback(
|
||||
() => {
|
||||
if (!stageRef.current) return;
|
||||
|
||||
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
|
||||
@ -119,37 +128,12 @@ const InpaintingCanvas = () => {
|
||||
|
||||
dispatch(setCursorPosition(scaledCursorPosition));
|
||||
|
||||
if (!maskLayerRef.current) {
|
||||
if (!maskLayerRef.current || !shouldLockBoundingBox) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deltaX = lastCursorPosition.current.x - scaledCursorPosition.x;
|
||||
const deltaY = lastCursorPosition.current.y - scaledCursorPosition.y;
|
||||
|
||||
lastCursorPosition.current = scaledCursorPosition;
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
dispatch(
|
||||
setBoundingBoxCoordinate({
|
||||
x,
|
||||
y,
|
||||
})
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
if (!isDrawing) return;
|
||||
|
||||
didMouseMoveRef.current = true;
|
||||
@ -157,20 +141,25 @@ const InpaintingCanvas = () => {
|
||||
dispatch(
|
||||
addPointToCurrentLine([scaledCursorPosition.x, scaledCursorPosition.y])
|
||||
);
|
||||
}, [
|
||||
dispatch,
|
||||
isMovingBoundingBox,
|
||||
boundingBoxDimensions,
|
||||
canvasDimensions,
|
||||
boundingBoxCoordinate,
|
||||
isDrawing,
|
||||
]);
|
||||
},
|
||||
[dispatch, isDrawing, shouldLockBoundingBox]
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* Canvas onMouseUp
|
||||
*
|
||||
*/
|
||||
const handleMouseUp = useCallback(() => {
|
||||
if (!didMouseMoveRef.current && isDrawing && stageRef.current) {
|
||||
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
|
||||
|
||||
if (!scaledCursorPosition || !maskLayerRef.current) return;
|
||||
if (
|
||||
!scaledCursorPosition ||
|
||||
!maskLayerRef.current ||
|
||||
!shouldLockBoundingBox
|
||||
)
|
||||
return;
|
||||
|
||||
/**
|
||||
* Extend the current line.
|
||||
@ -185,13 +174,23 @@ const InpaintingCanvas = () => {
|
||||
didMouseMoveRef.current = false;
|
||||
}
|
||||
dispatch(setIsDrawing(false));
|
||||
}, [dispatch, isDrawing]);
|
||||
}, [dispatch, isDrawing, shouldLockBoundingBox]);
|
||||
|
||||
/**
|
||||
*
|
||||
* Canvas onMouseOut
|
||||
*
|
||||
*/
|
||||
const handleMouseOutCanvas = useCallback(() => {
|
||||
dispatch(setCursorPosition(null));
|
||||
dispatch(setIsDrawing(false));
|
||||
}, [dispatch]);
|
||||
|
||||
/**
|
||||
*
|
||||
* Canvas onMouseEnter
|
||||
*
|
||||
*/
|
||||
const handleMouseEnter = useCallback(
|
||||
(e: KonvaEventObject<MouseEvent>) => {
|
||||
if (e.evt.buttons === 1) {
|
||||
@ -202,8 +201,7 @@ const InpaintingCanvas = () => {
|
||||
if (
|
||||
!scaledCursorPosition ||
|
||||
!maskLayerRef.current ||
|
||||
isMovingBoundingBox ||
|
||||
isTransformingBoundingBox
|
||||
!shouldLockBoundingBox
|
||||
)
|
||||
return;
|
||||
|
||||
@ -219,7 +217,7 @@ const InpaintingCanvas = () => {
|
||||
);
|
||||
}
|
||||
},
|
||||
[dispatch, brushSize, tool, isMovingBoundingBox, isTransformingBoundingBox]
|
||||
[dispatch, brushSize, tool, shouldLockBoundingBox]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import Konva from 'konva';
|
||||
import { KonvaEventObject } from 'konva/lib/Node';
|
||||
import { Box } from 'konva/lib/shapes/Transformer';
|
||||
import { Vector2d } from 'konva/lib/types';
|
||||
import _ from 'lodash';
|
||||
import { useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { Group, Rect, Transformer } from 'react-konva';
|
||||
import {
|
||||
RootState,
|
||||
@ -15,9 +16,6 @@ import {
|
||||
InpaintingState,
|
||||
setBoundingBoxCoordinate,
|
||||
setBoundingBoxDimensions,
|
||||
setIsTransformingBoundingBox,
|
||||
setIsDrawing,
|
||||
setShouldShowBrush,
|
||||
} from '../inpaintingSlice';
|
||||
import { rgbaColorToString } from '../util/colorToString';
|
||||
import {
|
||||
@ -35,7 +33,6 @@ const boundingBoxPreviewSelector = createSelector(
|
||||
canvasDimensions,
|
||||
stageScale,
|
||||
imageToInpaint,
|
||||
isMovingBoundingBox,
|
||||
shouldLockBoundingBox,
|
||||
} = inpainting;
|
||||
return {
|
||||
@ -47,7 +44,6 @@ const boundingBoxPreviewSelector = createSelector(
|
||||
imageToInpaint,
|
||||
dash: DASH_WIDTH / stageScale, // scale dash lengths
|
||||
strokeWidth: 1 / stageScale, // scale stroke thickness
|
||||
isMovingBoundingBox,
|
||||
shouldLockBoundingBox,
|
||||
};
|
||||
},
|
||||
@ -173,29 +169,31 @@ const InpaintingBoundingBoxPreview = () => {
|
||||
|
||||
const scaledStep = 64 * stageScale;
|
||||
|
||||
console.log(shouldLockBoundingBox);
|
||||
const handleOnDragMove = useCallback(
|
||||
(e: KonvaEventObject<DragEvent>) => {
|
||||
dispatch(setBoundingBoxCoordinate({ x: e.target.x(), y: e.target.y() }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Rect
|
||||
x={boundingBoxCoordinate.x}
|
||||
y={boundingBoxCoordinate.y}
|
||||
width={boundingBoxDimensions.width}
|
||||
height={boundingBoxDimensions.height}
|
||||
ref={shapeRef}
|
||||
stroke={'white'}
|
||||
strokeWidth={strokeWidth}
|
||||
listening={false}
|
||||
onTransformStart={() => {
|
||||
dispatch(setIsDrawing(false));
|
||||
dispatch(setShouldShowBrush(false));
|
||||
dispatch(setIsTransformingBoundingBox(true));
|
||||
}}
|
||||
onTransformEnd={() => {
|
||||
dispatch(setShouldShowBrush(true));
|
||||
dispatch(setIsTransformingBoundingBox(false));
|
||||
}}
|
||||
onTransform={() => {
|
||||
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
|
||||
@ -232,29 +230,10 @@ const InpaintingBoundingBoxPreview = () => {
|
||||
// Reset the scale now that the coords/dimensions have been un-scaled
|
||||
rect.scaleX(1);
|
||||
rect.scaleY(1);
|
||||
}}
|
||||
/>
|
||||
<Transformer
|
||||
enabledAnchors={shouldLockBoundingBox ? [] : undefined}
|
||||
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}
|
||||
ignoreStroke={true}
|
||||
keepRatio={false}
|
||||
flipEnabled={false}
|
||||
onMouseDown={(e: KonvaEventObject<MouseEvent>) => {
|
||||
e.cancelBubble = true;
|
||||
}}
|
||||
onMouseOver={(e: KonvaEventObject<MouseEvent>) => {
|
||||
e.cancelBubble = true;
|
||||
}}
|
||||
anchorDragBoundFunc={(
|
||||
}, [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
|
||||
@ -307,8 +286,12 @@ const InpaintingBoundingBoxPreview = () => {
|
||||
}
|
||||
|
||||
return newCoordinate;
|
||||
}}
|
||||
boundBoxFunc={(oldBoundBox, newBoundBox) => {
|
||||
},
|
||||
[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
|
||||
@ -317,8 +300,7 @@ const InpaintingBoundingBoxPreview = () => {
|
||||
if (!imageToInpaint) return oldBoundBox;
|
||||
|
||||
if (
|
||||
newBoundBox.width + newBoundBox.x >
|
||||
imageToInpaint.width * stageScale ||
|
||||
newBoundBox.width + newBoundBox.x > imageToInpaint.width * stageScale ||
|
||||
newBoundBox.height + newBoundBox.y >
|
||||
imageToInpaint.height * stageScale ||
|
||||
newBoundBox.x < 0 ||
|
||||
@ -328,7 +310,54 @@ const InpaintingBoundingBoxPreview = () => {
|
||||
}
|
||||
|
||||
return newBoundBox;
|
||||
},
|
||||
[imageToInpaint, stageScale]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Rect
|
||||
x={boundingBoxCoordinate.x}
|
||||
y={boundingBoxCoordinate.y}
|
||||
width={boundingBoxDimensions.width}
|
||||
height={boundingBoxDimensions.height}
|
||||
ref={shapeRef}
|
||||
stroke={'white'}
|
||||
strokeWidth={strokeWidth}
|
||||
listening={!shouldLockBoundingBox}
|
||||
onMouseEnter={(e) => {
|
||||
// style stage container:
|
||||
const container = e?.target?.getStage()?.container();
|
||||
if (!container) return;
|
||||
container.style.cursor = 'move';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
const container = e?.target?.getStage()?.container();
|
||||
if (!container) return;
|
||||
container.style.cursor = 'default';
|
||||
}}
|
||||
draggable={!shouldLockBoundingBox}
|
||||
onDragMove={handleOnDragMove}
|
||||
dragBoundFunc={dragBoundFunc}
|
||||
onTransform={handleOnTransform}
|
||||
/>
|
||||
<Transformer
|
||||
ref={transformerRef}
|
||||
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}
|
||||
keepRatio={false}
|
||||
listening={!shouldLockBoundingBox}
|
||||
enabledAnchors={shouldLockBoundingBox ? [] : undefined}
|
||||
boundBoxFunc={boundBoxFunc}
|
||||
anchorDragBoundFunc={anchorDragBoundFunc}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -10,7 +10,9 @@ import { OptionsState } from '../../../options/optionsSlice';
|
||||
import { tabMap } from '../../InvokeTabs';
|
||||
import {
|
||||
InpaintingState,
|
||||
setIsMovingBoundingBox,
|
||||
setIsDrawing,
|
||||
setShouldLockBoundingBox,
|
||||
setShouldShowBrush,
|
||||
toggleTool,
|
||||
} from '../inpaintingSlice';
|
||||
|
||||
@ -20,14 +22,12 @@ const keyboardEventManagerSelector = createSelector(
|
||||
const {
|
||||
shouldShowMask,
|
||||
cursorPosition,
|
||||
isMovingBoundingBox,
|
||||
shouldLockBoundingBox,
|
||||
} = inpainting;
|
||||
return {
|
||||
activeTabName: tabMap[options.activeTab],
|
||||
shouldShowMask,
|
||||
isCursorOnCanvas: Boolean(cursorPosition),
|
||||
isMovingBoundingBox,
|
||||
shouldLockBoundingBox,
|
||||
};
|
||||
},
|
||||
@ -44,7 +44,6 @@ const KeyboardEventManager = () => {
|
||||
shouldShowMask,
|
||||
activeTabName,
|
||||
isCursorOnCanvas,
|
||||
isMovingBoundingBox,
|
||||
shouldLockBoundingBox,
|
||||
} = useAppSelector(keyboardEventManagerSelector);
|
||||
|
||||
@ -54,11 +53,9 @@ const KeyboardEventManager = () => {
|
||||
useEffect(() => {
|
||||
const listener = (e: KeyboardEvent) => {
|
||||
if (
|
||||
!['Alt', ' '].includes(e.key) ||
|
||||
!['z', ' '].includes(e.key) ||
|
||||
activeTabName !== 'inpainting' ||
|
||||
!shouldShowMask ||
|
||||
e.repeat ||
|
||||
shouldLockBoundingBox
|
||||
!shouldShowMask
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -72,8 +69,10 @@ const KeyboardEventManager = () => {
|
||||
wasLastEventOverCanvas.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// cursor is over canvas
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (e.repeat) return;
|
||||
// cursor is over canvas, we can preventDefault now
|
||||
|
||||
// if this is the first event
|
||||
if (!lastEvent.current) {
|
||||
@ -87,15 +86,20 @@ const KeyboardEventManager = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
switch (e.key) {
|
||||
case 'Alt': {
|
||||
case 'z': {
|
||||
dispatch(toggleTool());
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -116,7 +120,7 @@ const KeyboardEventManager = () => {
|
||||
activeTabName,
|
||||
shouldShowMask,
|
||||
isCursorOnCanvas,
|
||||
isMovingBoundingBox,
|
||||
shouldLockBoundingBox,
|
||||
]);
|
||||
|
||||
return null;
|
||||
|
@ -291,7 +291,19 @@ export const inpaintingSlice = createSlice({
|
||||
};
|
||||
},
|
||||
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>) => {
|
||||
state.isMovingBoundingBox = action.payload;
|
||||
|
@ -69,35 +69,26 @@ export const inpaintingCanvasSelector = createSelector(
|
||||
shouldInvertMask,
|
||||
shouldShowMask,
|
||||
shouldShowCheckboardTransparency,
|
||||
shouldShowBrushPreview,
|
||||
imageToInpaint,
|
||||
isMovingBoundingBox,
|
||||
boundingBoxDimensions,
|
||||
canvasDimensions,
|
||||
boundingBoxCoordinate,
|
||||
stageScale,
|
||||
shouldShowBoundingBoxFill,
|
||||
isDrawing,
|
||||
isTransformingBoundingBox,
|
||||
shouldLockBoundingBox,
|
||||
shouldShowBoundingBox,
|
||||
|
||||
} = inpainting;
|
||||
return {
|
||||
tool,
|
||||
brushSize,
|
||||
maskColor,
|
||||
shouldInvertMask,
|
||||
shouldShowMask,
|
||||
shouldShowCheckboardTransparency,
|
||||
shouldShowBrushPreview,
|
||||
maskColor,
|
||||
imageToInpaint,
|
||||
isMovingBoundingBox,
|
||||
boundingBoxDimensions,
|
||||
canvasDimensions,
|
||||
boundingBoxCoordinate,
|
||||
stageScale,
|
||||
shouldShowBoundingBoxFill,
|
||||
isDrawing,
|
||||
isTransformingBoundingBox,
|
||||
shouldLockBoundingBox,
|
||||
shouldShowBoundingBox,
|
||||
};
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user