mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Improves bounding box interactions
- Bounding box can now be moved by dragging any of its edges - Bounding box does not affect drawing if already drawing a stroke - Can lock bounding box to draw directly on the bounding box edges - Removes spacebar-hold behaviour due to technical issues
This commit is contained in:
parent
d43bd4625d
commit
82dcbac28f
@ -113,7 +113,7 @@ const BoundingBoxSettings = () => {
|
|||||||
<div className="inpainting-bounding-box-settings">
|
<div className="inpainting-bounding-box-settings">
|
||||||
<div className="inpainting-bounding-box-header">
|
<div className="inpainting-bounding-box-header">
|
||||||
<p>Inpaint Box</p>
|
<p>Inpaint Box</p>
|
||||||
<IAIIconButton
|
{/* <IAIIconButton
|
||||||
aria-label="Toggle Bounding Box Visibility"
|
aria-label="Toggle Bounding Box Visibility"
|
||||||
icon={
|
icon={
|
||||||
shouldShowBoundingBox ? <BiShow size={22} /> : <BiHide size={22} />
|
shouldShowBoundingBox ? <BiShow size={22} /> : <BiHide size={22} />
|
||||||
@ -121,11 +121,12 @@ const BoundingBoxSettings = () => {
|
|||||||
onClick={handleShowBoundingBox}
|
onClick={handleShowBoundingBox}
|
||||||
background={'none'}
|
background={'none'}
|
||||||
padding={0}
|
padding={0}
|
||||||
/>
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
<div className="inpainting-bounding-box-settings-items">
|
<div className="inpainting-bounding-box-settings-items">
|
||||||
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
|
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
|
||||||
<IAISlider
|
<IAISlider
|
||||||
|
isDisabled={shouldLockBoundingBox}
|
||||||
label="Box W"
|
label="Box W"
|
||||||
min={64}
|
min={64}
|
||||||
max={roundDownToMultiple(canvasDimensions.width, 64)}
|
max={roundDownToMultiple(canvasDimensions.width, 64)}
|
||||||
@ -135,6 +136,7 @@ const BoundingBoxSettings = () => {
|
|||||||
width={'5rem'}
|
width={'5rem'}
|
||||||
/>
|
/>
|
||||||
<IAINumberInput
|
<IAINumberInput
|
||||||
|
isDisabled={shouldLockBoundingBox}
|
||||||
value={boundingBoxDimensions.width}
|
value={boundingBoxDimensions.width}
|
||||||
onChange={handleChangeBoundingBoxWidth}
|
onChange={handleChangeBoundingBoxWidth}
|
||||||
min={64}
|
min={64}
|
||||||
@ -143,6 +145,7 @@ const BoundingBoxSettings = () => {
|
|||||||
width={'5rem'}
|
width={'5rem'}
|
||||||
/>
|
/>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
|
isDisabled={shouldLockBoundingBox}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
aria-label={'Reset Width'}
|
aria-label={'Reset Width'}
|
||||||
tooltip={'Reset Width'}
|
tooltip={'Reset Width'}
|
||||||
@ -154,6 +157,7 @@ const BoundingBoxSettings = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
|
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
|
||||||
<IAISlider
|
<IAISlider
|
||||||
|
isDisabled={shouldLockBoundingBox}
|
||||||
label="Box H"
|
label="Box H"
|
||||||
min={64}
|
min={64}
|
||||||
max={roundDownToMultiple(canvasDimensions.height, 64)}
|
max={roundDownToMultiple(canvasDimensions.height, 64)}
|
||||||
@ -163,6 +167,7 @@ const BoundingBoxSettings = () => {
|
|||||||
width={'5rem'}
|
width={'5rem'}
|
||||||
/>
|
/>
|
||||||
<IAINumberInput
|
<IAINumberInput
|
||||||
|
isDisabled={shouldLockBoundingBox}
|
||||||
value={boundingBoxDimensions.height}
|
value={boundingBoxDimensions.height}
|
||||||
onChange={handleChangeBoundingBoxHeight}
|
onChange={handleChangeBoundingBoxHeight}
|
||||||
min={64}
|
min={64}
|
||||||
@ -172,6 +177,7 @@ const BoundingBoxSettings = () => {
|
|||||||
width={'5rem'}
|
width={'5rem'}
|
||||||
/>
|
/>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
|
isDisabled={shouldLockBoundingBox}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
aria-label={'Reset Height'}
|
aria-label={'Reset Height'}
|
||||||
tooltip={'Reset Height'}
|
tooltip={'Reset Height'}
|
||||||
|
@ -45,32 +45,40 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inpainting-canvas-wrapper {
|
.inpainting-canvas-spiner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inpainting-canvas-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
|
.inpainting-canvas-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.inpainting-alerts {
|
.inpainting-alerts {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
column-gap: 0.5rem;
|
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
padding: 0.5rem;
|
padding: 0.25rem;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
div {
|
button {
|
||||||
background-color: var(--accent-color);
|
&[data-selected='true'] {
|
||||||
color: var(--text-color);
|
svg {
|
||||||
padding: 0.2rem 0.6rem;
|
fill: var(--accent-color);
|
||||||
border-radius: 0.25rem;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,10 @@ import InpaintingBoundingBoxPreview, {
|
|||||||
} from './components/InpaintingBoundingBoxPreview';
|
} from './components/InpaintingBoundingBoxPreview';
|
||||||
import { KonvaEventObject } from 'konva/lib/Node';
|
import { KonvaEventObject } from 'konva/lib/Node';
|
||||||
import KeyboardEventManager from './components/KeyboardEventManager';
|
import KeyboardEventManager from './components/KeyboardEventManager';
|
||||||
import { useToast } from '@chakra-ui/react';
|
import { Icon, IconButton, Tooltip, useToast } from '@chakra-ui/react';
|
||||||
|
import { FaLock, FaUnlock } from 'react-icons/fa';
|
||||||
|
import { MdInvertColors, MdInvertColorsOff } from 'react-icons/md';
|
||||||
|
import { BiHide, BiShow } from 'react-icons/bi';
|
||||||
|
|
||||||
// Use a closure allow other components to use these things... not ideal...
|
// Use a closure allow other components to use these things... not ideal...
|
||||||
export let stageRef: MutableRefObject<StageType | null>;
|
export let stageRef: MutableRefObject<StageType | null>;
|
||||||
@ -58,6 +61,10 @@ const InpaintingCanvas = () => {
|
|||||||
isDrawing,
|
isDrawing,
|
||||||
shouldLockBoundingBox,
|
shouldLockBoundingBox,
|
||||||
boundingBoxDimensions,
|
boundingBoxDimensions,
|
||||||
|
// isTransformingBoundingBox,
|
||||||
|
isMouseOverBoundingBox,
|
||||||
|
isModifyingBoundingBox,
|
||||||
|
stageCursor,
|
||||||
} = useAppSelector(inpaintingCanvasSelector);
|
} = useAppSelector(inpaintingCanvasSelector);
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
@ -113,7 +120,7 @@ const InpaintingCanvas = () => {
|
|||||||
if (
|
if (
|
||||||
!scaledCursorPosition ||
|
!scaledCursorPosition ||
|
||||||
!maskLayerRef.current ||
|
!maskLayerRef.current ||
|
||||||
!shouldLockBoundingBox
|
isModifyingBoundingBox
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -127,7 +134,7 @@ const InpaintingCanvas = () => {
|
|||||||
points: [scaledCursorPosition.x, scaledCursorPosition.y],
|
points: [scaledCursorPosition.x, scaledCursorPosition.y],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}, [dispatch, brushSize, tool, shouldLockBoundingBox]);
|
}, [dispatch, brushSize, tool, isModifyingBoundingBox]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -143,20 +150,20 @@ const InpaintingCanvas = () => {
|
|||||||
|
|
||||||
dispatch(setCursorPosition(scaledCursorPosition));
|
dispatch(setCursorPosition(scaledCursorPosition));
|
||||||
|
|
||||||
if (!maskLayerRef.current || !shouldLockBoundingBox) {
|
if (!maskLayerRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastCursorPosition.current = scaledCursorPosition;
|
lastCursorPosition.current = scaledCursorPosition;
|
||||||
|
|
||||||
if (!isDrawing) return;
|
if (!isDrawing || isModifyingBoundingBox) return;
|
||||||
|
|
||||||
didMouseMoveRef.current = true;
|
didMouseMoveRef.current = true;
|
||||||
// Extend the current line
|
// Extend the current line
|
||||||
dispatch(
|
dispatch(
|
||||||
addPointToCurrentLine([scaledCursorPosition.x, scaledCursorPosition.y])
|
addPointToCurrentLine([scaledCursorPosition.x, scaledCursorPosition.y])
|
||||||
);
|
);
|
||||||
}, [dispatch, isDrawing, shouldLockBoundingBox]);
|
}, [dispatch, isDrawing, isModifyingBoundingBox]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -170,7 +177,7 @@ const InpaintingCanvas = () => {
|
|||||||
if (
|
if (
|
||||||
!scaledCursorPosition ||
|
!scaledCursorPosition ||
|
||||||
!maskLayerRef.current ||
|
!maskLayerRef.current ||
|
||||||
!shouldLockBoundingBox
|
isModifyingBoundingBox
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -187,7 +194,7 @@ const InpaintingCanvas = () => {
|
|||||||
didMouseMoveRef.current = false;
|
didMouseMoveRef.current = false;
|
||||||
}
|
}
|
||||||
dispatch(setIsDrawing(false));
|
dispatch(setIsDrawing(false));
|
||||||
}, [dispatch, isDrawing, shouldLockBoundingBox]);
|
}, [dispatch, isDrawing, isModifyingBoundingBox]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -214,7 +221,7 @@ const InpaintingCanvas = () => {
|
|||||||
if (
|
if (
|
||||||
!scaledCursorPosition ||
|
!scaledCursorPosition ||
|
||||||
!maskLayerRef.current ||
|
!maskLayerRef.current ||
|
||||||
!shouldLockBoundingBox
|
isModifyingBoundingBox
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -230,93 +237,117 @@ const InpaintingCanvas = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, brushSize, tool, shouldLockBoundingBox]
|
[dispatch, brushSize, tool, isModifyingBoundingBox]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inpainting-canvas-wrapper" tabIndex={1}>
|
<div className="inpainting-canvas-container" tabIndex={1}>
|
||||||
<div className="inpainting-alerts">
|
<div className="inpainting-canvas-wrapper">
|
||||||
{!shouldShowMask && (
|
<div className="inpainting-alerts">
|
||||||
<div style={{ pointerEvents: 'none' }}>Mask Hidden (H)</div>
|
|
||||||
)}
|
|
||||||
{shouldInvertMask && (
|
|
||||||
<div style={{ pointerEvents: 'none' }}>Mask Inverted (Shift+M)</div>
|
|
||||||
)}
|
|
||||||
{!shouldLockBoundingBox && (
|
|
||||||
<div style={{ pointerEvents: 'none' }}>
|
<div style={{ pointerEvents: 'none' }}>
|
||||||
{`Transforming Bounding Box ${boundingBoxDimensions.width}x${boundingBoxDimensions.height} (M)`}
|
<IconButton
|
||||||
|
aria-label="Show/HideMask"
|
||||||
|
size="xs"
|
||||||
|
variant={'ghost'}
|
||||||
|
fontSize={'1rem'}
|
||||||
|
data-selected={!shouldShowMask}
|
||||||
|
icon={shouldShowMask ? <BiShow /> : <BiHide />}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div style={{ pointerEvents: 'none' }}>
|
||||||
</div>
|
<IconButton
|
||||||
|
aria-label="Invert Mask"
|
||||||
|
size="xs"
|
||||||
|
variant={'ghost'}
|
||||||
|
fontSize={'1rem'}
|
||||||
|
data-selected={shouldInvertMask}
|
||||||
|
icon={
|
||||||
|
shouldInvertMask ? <MdInvertColors /> : <MdInvertColorsOff />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ pointerEvents: 'none' }}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Bounding Box Lock"
|
||||||
|
size="xs"
|
||||||
|
variant={'ghost'}
|
||||||
|
data-selected={shouldLockBoundingBox}
|
||||||
|
icon={shouldLockBoundingBox ? <FaLock /> : <FaUnlock />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{canvasBgImage && (
|
{canvasBgImage && (
|
||||||
<Stage
|
<Stage
|
||||||
width={Math.floor(canvasBgImage.width * stageScale)}
|
width={Math.floor(canvasBgImage.width * stageScale)}
|
||||||
height={Math.floor(canvasBgImage.height * stageScale)}
|
height={Math.floor(canvasBgImage.height * stageScale)}
|
||||||
scale={{ x: stageScale, y: stageScale }}
|
scale={{ x: stageScale, y: stageScale }}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseUp={handleMouseUp}
|
onMouseUp={handleMouseUp}
|
||||||
onMouseOut={handleMouseOutCanvas}
|
onMouseOut={handleMouseOutCanvas}
|
||||||
onMouseLeave={handleMouseOutCanvas}
|
onMouseLeave={handleMouseOutCanvas}
|
||||||
style={{ cursor: shouldShowMask ? 'none' : 'default' }}
|
style={{ ...(stageCursor ? { cursor: stageCursor } : {}) }}
|
||||||
className="inpainting-canvas-stage checkerboard"
|
className="inpainting-canvas-stage checkerboard"
|
||||||
ref={stageRef}
|
ref={stageRef}
|
||||||
>
|
>
|
||||||
{!shouldInvertMask && !shouldShowCheckboardTransparency && (
|
{!shouldInvertMask && !shouldShowCheckboardTransparency && (
|
||||||
<Layer name={'image-layer'} listening={false}>
|
<Layer name={'image-layer'} listening={false}>
|
||||||
<KonvaImage listening={false} image={canvasBgImage} />
|
<KonvaImage listening={false} image={canvasBgImage} />
|
||||||
</Layer>
|
|
||||||
)}
|
|
||||||
{shouldShowMask && (
|
|
||||||
<>
|
|
||||||
<Layer
|
|
||||||
name={'mask-layer'}
|
|
||||||
listening={false}
|
|
||||||
opacity={
|
|
||||||
shouldShowCheckboardTransparency || shouldInvertMask
|
|
||||||
? 1
|
|
||||||
: maskColor.a
|
|
||||||
}
|
|
||||||
ref={maskLayerRef}
|
|
||||||
>
|
|
||||||
<InpaintingCanvasLines />
|
|
||||||
|
|
||||||
{shouldLockBoundingBox && <InpaintingCanvasBrushPreview />}
|
|
||||||
|
|
||||||
{shouldInvertMask && (
|
|
||||||
<KonvaImage
|
|
||||||
image={canvasBgImage}
|
|
||||||
listening={false}
|
|
||||||
globalCompositeOperation="source-in"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!shouldInvertMask && shouldShowCheckboardTransparency && (
|
|
||||||
<KonvaImage
|
|
||||||
image={canvasBgImage}
|
|
||||||
listening={false}
|
|
||||||
globalCompositeOperation="source-out"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Layer>
|
</Layer>
|
||||||
{shouldShowMask && (
|
)}
|
||||||
<Layer>
|
{shouldShowMask && (
|
||||||
{shouldShowBoundingBoxFill && shouldShowBoundingBox && (
|
<>
|
||||||
<InpaintingBoundingBoxPreviewOverlay />
|
<Layer
|
||||||
|
name={'mask-layer'}
|
||||||
|
listening={false}
|
||||||
|
opacity={
|
||||||
|
shouldShowCheckboardTransparency || shouldInvertMask
|
||||||
|
? 1
|
||||||
|
: maskColor.a
|
||||||
|
}
|
||||||
|
ref={maskLayerRef}
|
||||||
|
>
|
||||||
|
<InpaintingCanvasLines />
|
||||||
|
|
||||||
|
{!isMouseOverBoundingBox && !isModifyingBoundingBox && (
|
||||||
|
<InpaintingCanvasBrushPreview />
|
||||||
)}
|
)}
|
||||||
{shouldShowBoundingBox && <InpaintingBoundingBoxPreview />}
|
|
||||||
{shouldLockBoundingBox && (
|
{shouldInvertMask && (
|
||||||
<InpaintingCanvasBrushPreviewOutline />
|
<KonvaImage
|
||||||
|
image={canvasBgImage}
|
||||||
|
listening={false}
|
||||||
|
globalCompositeOperation="source-in"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!shouldInvertMask && shouldShowCheckboardTransparency && (
|
||||||
|
<KonvaImage
|
||||||
|
image={canvasBgImage}
|
||||||
|
listening={false}
|
||||||
|
globalCompositeOperation="source-out"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Layer>
|
</Layer>
|
||||||
)}
|
{shouldShowMask && (
|
||||||
</>
|
<Layer>
|
||||||
)}
|
{shouldShowBoundingBoxFill && shouldShowBoundingBox && (
|
||||||
</Stage>
|
<InpaintingBoundingBoxPreviewOverlay />
|
||||||
)}
|
)}
|
||||||
<Cacher />
|
<InpaintingBoundingBoxPreview />
|
||||||
<KeyboardEventManager />
|
{!isMouseOverBoundingBox && !isModifyingBoundingBox && (
|
||||||
|
<InpaintingCanvasBrushPreviewOutline />
|
||||||
|
)}
|
||||||
|
</Layer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Stage>
|
||||||
|
)}
|
||||||
|
<Cacher />
|
||||||
|
<KeyboardEventManager />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -33,6 +33,8 @@ const Cacher = () => {
|
|||||||
futureLines,
|
futureLines,
|
||||||
needsCache,
|
needsCache,
|
||||||
isDrawing,
|
isDrawing,
|
||||||
|
isTransformingBoundingBox,
|
||||||
|
isMovingBoundingBox,
|
||||||
} = useAppSelector((state: RootState) => state.inpainting);
|
} = useAppSelector((state: RootState) => state.inpainting);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@ -64,6 +66,8 @@ const Cacher = () => {
|
|||||||
futureLines,
|
futureLines,
|
||||||
needsCache,
|
needsCache,
|
||||||
isDrawing,
|
isDrawing,
|
||||||
|
isTransformingBoundingBox,
|
||||||
|
isMovingBoundingBox,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,7 +4,7 @@ import { KonvaEventObject } from 'konva/lib/Node';
|
|||||||
import { Box } from 'konva/lib/shapes/Transformer';
|
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 { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { Group, Rect, Transformer } from 'react-konva';
|
import { Group, Rect, Transformer } from 'react-konva';
|
||||||
import {
|
import {
|
||||||
RootState,
|
RootState,
|
||||||
@ -17,6 +17,9 @@ import {
|
|||||||
InpaintingState,
|
InpaintingState,
|
||||||
setBoundingBoxCoordinate,
|
setBoundingBoxCoordinate,
|
||||||
setBoundingBoxDimensions,
|
setBoundingBoxDimensions,
|
||||||
|
setIsMouseOverBoundingBox,
|
||||||
|
setIsMovingBoundingBox,
|
||||||
|
setIsTransformingBoundingBox,
|
||||||
} from '../inpaintingSlice';
|
} from '../inpaintingSlice';
|
||||||
import { rgbaColorToString } from '../util/colorToString';
|
import { rgbaColorToString } from '../util/colorToString';
|
||||||
import {
|
import {
|
||||||
@ -35,6 +38,10 @@ const boundingBoxPreviewSelector = createSelector(
|
|||||||
stageScale,
|
stageScale,
|
||||||
imageToInpaint,
|
imageToInpaint,
|
||||||
shouldLockBoundingBox,
|
shouldLockBoundingBox,
|
||||||
|
isDrawing,
|
||||||
|
isTransformingBoundingBox,
|
||||||
|
isMovingBoundingBox,
|
||||||
|
isMouseOverBoundingBox,
|
||||||
} = inpainting;
|
} = inpainting;
|
||||||
return {
|
return {
|
||||||
boundingBoxCoordinate,
|
boundingBoxCoordinate,
|
||||||
@ -46,6 +53,10 @@ 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
|
||||||
shouldLockBoundingBox,
|
shouldLockBoundingBox,
|
||||||
|
isDrawing,
|
||||||
|
isTransformingBoundingBox,
|
||||||
|
isMouseOverBoundingBox,
|
||||||
|
isMovingBoundingBox,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -93,10 +104,13 @@ const InpaintingBoundingBoxPreview = () => {
|
|||||||
const {
|
const {
|
||||||
boundingBoxCoordinate,
|
boundingBoxCoordinate,
|
||||||
boundingBoxDimensions,
|
boundingBoxDimensions,
|
||||||
strokeWidth,
|
|
||||||
stageScale,
|
stageScale,
|
||||||
imageToInpaint,
|
imageToInpaint,
|
||||||
shouldLockBoundingBox,
|
shouldLockBoundingBox,
|
||||||
|
isDrawing,
|
||||||
|
isTransformingBoundingBox,
|
||||||
|
isMovingBoundingBox,
|
||||||
|
isMouseOverBoundingBox,
|
||||||
} = useAppSelector(boundingBoxPreviewSelector);
|
} = useAppSelector(boundingBoxPreviewSelector);
|
||||||
|
|
||||||
const transformerRef = useRef<Konva.Transformer>(null);
|
const transformerRef = useRef<Konva.Transformer>(null);
|
||||||
@ -108,15 +122,6 @@ const InpaintingBoundingBoxPreview = () => {
|
|||||||
transformerRef.current.getLayer()?.batchDraw();
|
transformerRef.current.getLayer()?.batchDraw();
|
||||||
}, [shouldLockBoundingBox]);
|
}, [shouldLockBoundingBox]);
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => () => {
|
|
||||||
const container = stageRef.current?.container();
|
|
||||||
if (!container) return;
|
|
||||||
container.style.cursor = 'unset';
|
|
||||||
},
|
|
||||||
[shouldLockBoundingBox]
|
|
||||||
);
|
|
||||||
|
|
||||||
const scaledStep = 64 * stageScale;
|
const scaledStep = 64 * stageScale;
|
||||||
|
|
||||||
const handleOnDragMove = useCallback(
|
const handleOnDragMove = useCallback(
|
||||||
@ -269,6 +274,29 @@ const InpaintingBoundingBoxPreview = () => {
|
|||||||
[imageToInpaint, stageScale]
|
[imageToInpaint, stageScale]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleStartedTransforming = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
|
e.cancelBubble = true;
|
||||||
|
e.evt.stopImmediatePropagation();
|
||||||
|
dispatch(setIsTransformingBoundingBox(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEndedTransforming = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
|
dispatch(setIsTransformingBoundingBox(false));
|
||||||
|
dispatch(setIsMouseOverBoundingBox(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStartedMoving = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
|
e.cancelBubble = true;
|
||||||
|
e.evt.stopImmediatePropagation();
|
||||||
|
dispatch(setIsMovingBoundingBox(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEndedModifying = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
|
dispatch(setIsTransformingBoundingBox(false));
|
||||||
|
dispatch(setIsMovingBoundingBox(false));
|
||||||
|
dispatch(setIsMouseOverBoundingBox(false));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Rect
|
<Rect
|
||||||
@ -277,23 +305,27 @@ const InpaintingBoundingBoxPreview = () => {
|
|||||||
width={boundingBoxDimensions.width}
|
width={boundingBoxDimensions.width}
|
||||||
height={boundingBoxDimensions.height}
|
height={boundingBoxDimensions.height}
|
||||||
ref={shapeRef}
|
ref={shapeRef}
|
||||||
stroke={'white'}
|
stroke={isMouseOverBoundingBox ? 'rgba(255,255,255,0.3)' : 'white'}
|
||||||
strokeWidth={strokeWidth}
|
strokeWidth={Math.floor((isMouseOverBoundingBox ? 8 : 1) / stageScale)}
|
||||||
listening={!shouldLockBoundingBox}
|
fillEnabled={false}
|
||||||
onMouseEnter={(e) => {
|
hitStrokeWidth={Math.floor(13 / stageScale)}
|
||||||
const container = e?.target?.getStage()?.container();
|
listening={!isDrawing && !shouldLockBoundingBox}
|
||||||
if (!container) return;
|
onMouseOver={() => {
|
||||||
container.style.cursor = shouldLockBoundingBox ? 'none' : 'move';
|
dispatch(setIsMouseOverBoundingBox(true));
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseOut={() => {
|
||||||
const container = e?.target?.getStage()?.container();
|
!isTransformingBoundingBox &&
|
||||||
if (!container) return;
|
!isMovingBoundingBox &&
|
||||||
container.style.cursor = shouldLockBoundingBox ? 'none' : 'default';
|
dispatch(setIsMouseOverBoundingBox(false));
|
||||||
}}
|
}}
|
||||||
draggable={!shouldLockBoundingBox}
|
onMouseDown={handleStartedMoving}
|
||||||
|
onMouseUp={handleEndedModifying}
|
||||||
|
draggable={true}
|
||||||
onDragMove={handleOnDragMove}
|
onDragMove={handleOnDragMove}
|
||||||
dragBoundFunc={dragBoundFunc}
|
dragBoundFunc={dragBoundFunc}
|
||||||
onTransform={handleOnTransform}
|
onTransform={handleOnTransform}
|
||||||
|
onDragEnd={handleEndedModifying}
|
||||||
|
onTransformEnd={handleEndedTransforming}
|
||||||
/>
|
/>
|
||||||
<Transformer
|
<Transformer
|
||||||
ref={transformerRef}
|
ref={transformerRef}
|
||||||
@ -308,10 +340,22 @@ const InpaintingBoundingBoxPreview = () => {
|
|||||||
flipEnabled={false}
|
flipEnabled={false}
|
||||||
ignoreStroke={true}
|
ignoreStroke={true}
|
||||||
keepRatio={false}
|
keepRatio={false}
|
||||||
listening={!shouldLockBoundingBox}
|
listening={!isDrawing && !shouldLockBoundingBox}
|
||||||
|
onMouseDown={handleStartedTransforming}
|
||||||
|
onMouseUp={handleEndedTransforming}
|
||||||
enabledAnchors={shouldLockBoundingBox ? [] : undefined}
|
enabledAnchors={shouldLockBoundingBox ? [] : undefined}
|
||||||
boundBoxFunc={boundBoxFunc}
|
boundBoxFunc={boundBoxFunc}
|
||||||
anchorDragBoundFunc={anchorDragBoundFunc}
|
anchorDragBoundFunc={anchorDragBoundFunc}
|
||||||
|
onDragEnd={handleEndedModifying}
|
||||||
|
onTransformEnd={handleEndedTransforming}
|
||||||
|
onMouseOver={() => {
|
||||||
|
dispatch(setIsMouseOverBoundingBox(true));
|
||||||
|
}}
|
||||||
|
onMouseOut={() => {
|
||||||
|
!isTransformingBoundingBox &&
|
||||||
|
!isMovingBoundingBox &&
|
||||||
|
dispatch(setIsMouseOverBoundingBox(false));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -40,18 +40,10 @@ const inpaintingCanvasBrushPreviewSelector = createSelector(
|
|||||||
* Draws a black circle around the canvas brush preview.
|
* Draws a black circle around the canvas brush preview.
|
||||||
*/
|
*/
|
||||||
const InpaintingCanvasBrushPreview = () => {
|
const InpaintingCanvasBrushPreview = () => {
|
||||||
const {
|
const { cursorPosition, width, height, brushSize, maskColorString, tool } =
|
||||||
cursorPosition,
|
useAppSelector(inpaintingCanvasBrushPreviewSelector);
|
||||||
width,
|
|
||||||
height,
|
|
||||||
shouldShowBrushPreview,
|
|
||||||
brushSize,
|
|
||||||
maskColorString,
|
|
||||||
tool,
|
|
||||||
shouldShowBrush,
|
|
||||||
} = useAppSelector(inpaintingCanvasBrushPreviewSelector);
|
|
||||||
|
|
||||||
if (!shouldShowBrush || !(cursorPosition || shouldShowBrushPreview)) {
|
if (!cursorPosition) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,9 @@ export interface InpaintingState {
|
|||||||
needsCache: boolean;
|
needsCache: boolean;
|
||||||
stageScale: number;
|
stageScale: number;
|
||||||
isDrawing: boolean;
|
isDrawing: boolean;
|
||||||
|
isTransformingBoundingBox: boolean;
|
||||||
|
isMouseOverBoundingBox: boolean;
|
||||||
|
isMovingBoundingBox: boolean;
|
||||||
shouldUseInpaintReplace: boolean;
|
shouldUseInpaintReplace: boolean;
|
||||||
inpaintReplace: number;
|
inpaintReplace: number;
|
||||||
shouldLockBoundingBox: boolean;
|
shouldLockBoundingBox: boolean;
|
||||||
@ -63,7 +66,7 @@ const initialInpaintingState: InpaintingState = {
|
|||||||
canvasDimensions: { width: 0, height: 0 },
|
canvasDimensions: { width: 0, height: 0 },
|
||||||
boundingBoxDimensions: { width: 512, height: 512 },
|
boundingBoxDimensions: { width: 512, height: 512 },
|
||||||
boundingBoxCoordinate: { x: 0, y: 0 },
|
boundingBoxCoordinate: { x: 0, y: 0 },
|
||||||
boundingBoxPreviewFill: { r: 0, g: 0, b: 0, a: 0.7 },
|
boundingBoxPreviewFill: { r: 0, g: 0, b: 0, a: 0.5 },
|
||||||
shouldShowBoundingBox: true,
|
shouldShowBoundingBox: true,
|
||||||
shouldShowBoundingBoxFill: true,
|
shouldShowBoundingBoxFill: true,
|
||||||
cursorPosition: null,
|
cursorPosition: null,
|
||||||
@ -77,6 +80,9 @@ const initialInpaintingState: InpaintingState = {
|
|||||||
shouldShowBrushPreview: false,
|
shouldShowBrushPreview: false,
|
||||||
needsCache: false,
|
needsCache: false,
|
||||||
isDrawing: false,
|
isDrawing: false,
|
||||||
|
isTransformingBoundingBox: false,
|
||||||
|
isMouseOverBoundingBox: false,
|
||||||
|
isMovingBoundingBox: false,
|
||||||
stageScale: 1,
|
stageScale: 1,
|
||||||
shouldUseInpaintReplace: false,
|
shouldUseInpaintReplace: false,
|
||||||
inpaintReplace: 1,
|
inpaintReplace: 1,
|
||||||
@ -319,6 +325,15 @@ export const inpaintingSlice = createSlice({
|
|||||||
setShouldShowBoundingBox: (state, action: PayloadAction<boolean>) => {
|
setShouldShowBoundingBox: (state, action: PayloadAction<boolean>) => {
|
||||||
state.shouldShowBoundingBox = action.payload;
|
state.shouldShowBoundingBox = action.payload;
|
||||||
},
|
},
|
||||||
|
setIsTransformingBoundingBox: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.isTransformingBoundingBox = action.payload;
|
||||||
|
},
|
||||||
|
setIsMovingBoundingBox: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.isMovingBoundingBox = action.payload;
|
||||||
|
},
|
||||||
|
setIsMouseOverBoundingBox: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.isMouseOverBoundingBox = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -354,6 +369,9 @@ export const {
|
|||||||
setInpaintReplace,
|
setInpaintReplace,
|
||||||
setShouldLockBoundingBox,
|
setShouldLockBoundingBox,
|
||||||
toggleShouldLockBoundingBox,
|
toggleShouldLockBoundingBox,
|
||||||
|
setIsMovingBoundingBox,
|
||||||
|
setIsTransformingBoundingBox,
|
||||||
|
setIsMouseOverBoundingBox,
|
||||||
} = inpaintingSlice.actions;
|
} = inpaintingSlice.actions;
|
||||||
|
|
||||||
export default inpaintingSlice.reducer;
|
export default inpaintingSlice.reducer;
|
||||||
|
@ -78,7 +78,23 @@ export const inpaintingCanvasSelector = createSelector(
|
|||||||
isDrawing,
|
isDrawing,
|
||||||
shouldLockBoundingBox,
|
shouldLockBoundingBox,
|
||||||
boundingBoxDimensions,
|
boundingBoxDimensions,
|
||||||
|
isTransformingBoundingBox,
|
||||||
|
isMouseOverBoundingBox,
|
||||||
|
isMovingBoundingBox,
|
||||||
} = inpainting;
|
} = inpainting;
|
||||||
|
|
||||||
|
let stageCursor: string | undefined = '';
|
||||||
|
|
||||||
|
if (isTransformingBoundingBox) {
|
||||||
|
stageCursor = undefined;
|
||||||
|
} else if (isMovingBoundingBox || isMouseOverBoundingBox) {
|
||||||
|
stageCursor = 'move';
|
||||||
|
} else if (shouldShowMask) {
|
||||||
|
stageCursor = 'none';
|
||||||
|
} else {
|
||||||
|
stageCursor = 'default';
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tool,
|
tool,
|
||||||
brushSize,
|
brushSize,
|
||||||
@ -93,6 +109,10 @@ export const inpaintingCanvasSelector = createSelector(
|
|||||||
isDrawing,
|
isDrawing,
|
||||||
shouldLockBoundingBox,
|
shouldLockBoundingBox,
|
||||||
boundingBoxDimensions,
|
boundingBoxDimensions,
|
||||||
|
isTransformingBoundingBox,
|
||||||
|
isModifyingBoundingBox: isTransformingBoundingBox || isMovingBoundingBox,
|
||||||
|
stageCursor,
|
||||||
|
isMouseOverBoundingBox,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user